Files
rbx/x05-rq/src/rq.erl
T

692 lines
20 KiB
Erlang

%% @doc rq: Decentralized Identity Namespace Disambiguation Utility
-module(rq).
-vsn("0.1.0").
-author("Peter Harpending").
-copyright("Peter Harpending").
-export([start/1]).
-compile([export_all,nowarn_export_all]).
% TODO: rename "decode" to either "decompose", "compost" or "debug"
% deploy opts
-record(rdo,
{deploy_file = "rq.deploy" :: file:filename(),
op = deploy :: deploy | tx | signed_tx}).
-spec start(ArgV) -> ok
when ArgV :: [string()].
start(ArgV) ->
ok = hz(),
_ = application:ensure_all_started(ssl),
rq(ArgV).
help_screen() ->
"""
rq = Decentralized Identity Namespace Disambiguation Utility
version 0.0.0 + 0.0.1i
USAGE:
rq [-s | --shell] [ARGS]
equivalent to `rq ARGS`, but starts erlang shell with
rq&deps in context; with no args, simply starts erlang
shell.
rq [-h | --help]
show this menu
rq debug
show variables
rq addr names
rq addr names.aes
rq ctstr names
rq ctstr names.aes deployed address of names
rq decode cb_... deep decode of api string but only the ones i use
rq decode tx_... deep decode of api string but only the ones i use
rq deploy
rq deploy --tx debug ContractCreateTx
rq deploy --tx-only
rq deploy --tx_only
rq deploy --signed debug SignedTx
rq deploy --signed-tx
rq deploy --signed_tx
rq grids URL only works with the faucet grids
rq my
rq my everything all
rq my akstr my akstr
rq my acc
rq my balance
rq my explorer
rq my explorer_link
rq my explorer_url
rq my url
rq my rbx_tuples
rq my tuples my rbx_tuples
rq query ak_... hz:acc("ak_...")
rq query ct_... hz:contract("ak_...")
rq query th_... hz:contract("ak_...")
rq lookup NAME
rq register NAME
rq src names.aes print contract source
rq src rubicon.aes print contract source
""".
rq([]) -> rq_shell([]);
rq(["-s" | Rest]) -> rq_shell(Rest);
rq(["--shell" | Rest]) -> rq_shell(Rest);
rq(Rest) -> rq_noshell(Rest).
rq_shell(Args) ->
ok = shell:start_interactive(),
case Args of
[] -> ok;
More -> rq_II(More)
end.
rq_noshell(Args) ->
rq_II(Args),
zx:silent_stop().
%% @private
%% dispatch function
rq_II(Args) ->
case Args of
[Addr, Names] when ((Addr =:= "addr") orelse Addr =:= "ctstr") andalso
(Names =:= "names.aes" orelse Names =:= "names") ->
io:format("~ts~n", [nct_ctstr()]);
["debug" | Rest] ->
rq_debug(Rest);
["decode", ApiStr] -> rq_decode(ApiStr);
["deploy"] -> rq_deploy(#rdo{});
["deploy", "--signed"] -> rq_deploy(#rdo{op = signed_tx});
["deploy", "--signed-tx"] -> rq_deploy(#rdo{op = signed_tx});
["deploy", "--signed_tx"] -> rq_deploy(#rdo{op = signed_tx});
["deploy", "--tx"] -> rq_deploy(#rdo{op = tx});
["deploy", "--tx-only"] -> rq_deploy(#rdo{op = tx});
["deploy", "--tx_only"] -> rq_deploy(#rdo{op = tx});
["grids", URL] -> rq_grids(URL);
["my"] -> rq_my(everything);
["my", "everything"] -> rq_my(everything);
["my", "acc"] -> rq_my(acc);
["my", "addr"] -> rq_my(akstr);
["my", "akstr"] -> rq_my(akstr);
["my", "balance"] -> rq_my(balance);
["my", "explorer"] -> rq_my(url);
["my", "explorer_link"] -> rq_my(url);
["my", "explorer_url"] -> rq_my(url);
["my", "url"] -> rq_my(url);
["my", "rbx_tuples"] -> rq_my(rbx_tuples);
["my", "tuples"] -> rq_my(rbx_tuples);
["lookup", Name] -> rq_lookup(Name);
["register", Name] -> rq_register(Name);
["query" | Args2] -> rq_query(Args2);
["src" | Args2] -> rq_src(Args2);
["-h" | Args2] -> rq_help(Args2);
["--help" | Args2] -> rq_help(Args2);
BadArgs -> badargs(BadArgs)
end.
rq_deploy(Opts = #rdo{deploy_file = F}) ->
case file:read_file(F) of
{ok, B} ->
io:format("already deployed brother~n~ts~n", [B]),
error(low_iq);
_Error ->
rq_deploy_II(Opts)
end.
rq_deploy_II(#rdo{op = Op}) ->
{ok, CreateTx} = hz:contract_create(my_akstr(), rct_filepath(), {erlang, [my_rbx_tuples()]}),
case Op of
deploy ->
sign_and_post(CreateTx);
tx ->
rq_decode(CreateTx);
signed_tx ->
Signed = hz:sign_tx(CreateTx, my_secret_key()),
rq_decode(Signed);
NYI ->
io:format("NYI: ~tp~n", [NYI]),
error(ur_mom_is_an_op)
end.
sign_and_post(UnsignedTx) ->
SignedTx = hz:sign_tx(UnsignedTx, my_secret_key()),
{ok, PostResult} = hz:post_tx(SignedTx),
case PostResult of
#{"tx_hash" := ThStr} ->
rq_query([ThStr]);
#{"reason" := NodeErr} ->
io:format("ERROR: node sent back the following extremely helpful error message~n", []),
io:format("~tp~n", [NodeErr]),
error(NodeErr);
_ ->
io:format("ERROR (nyi) node sent back voodoo:~n", []),
io:format("PostResult = ~tp~n", [PostResult]),
error(nyi)
end.
rq_decode(TxStr = "tx_" ++ _) ->
rq_decode(unicode:characters_to_binary(TxStr));
rq_decode(TxBin = <<"tx_", _/binary>>) ->
io:format("TxBin = ~tp~n", [TxBin]),
{transaction, Bytes} = gmser_api_encoder:decode(TxBin),
just_the_tip_decode(Bytes),
ok;
rq_decode(CbStr = "cb_" ++ _) ->
rq_decode(unicode:characters_to_binary(CbStr));
rq_decode(CbBin = <<"cb_", _/binary>>) ->
io:format("CbBin = ~tp~n", [CbBin]),
{contract_bytearray, Bytes} = gmser_api_encoder:decode(CbBin),
io:format("Bytes = ~tp~n", [Bytes]),
Deser = gmb_fate_encoding:deserialize(Bytes),
io:format("Deserialized = ~tp~n", [Deser]),
Fmt = gmb_fate_data:format(Deser),
io:format("Formatted = ~ts~n", [Fmt]),
ok.
% this is like rq_decode but in the case where the data at hand is
% NOT api-encoded; a typical example would be debugging a signed
% transaction; one of the fields is the signatee, which needs to be
% decomposed
just_the_tip_decode(Bytes) ->
io:format("Bytes = ~tp~n", [Bytes]),
RLP = gmser_rlp:decode(Bytes),
io:format("RLP = ~tp~n", [RLP]),
[TagBin, VsnBin | Fields] = RLP,
Tag = binary:decode_unsigned(TagBin),
RevTag = rev_tag(Tag),
Vsn = binary:decode_unsigned(VsnBin),
io:format("Tag = ~tp~n", [Tag]),
io:format("RevTag = ~tp~n", [RevTag]),
io:format("Vsn = ~tp~n", [Vsn]),
io:format("Fields = ~tp~n", [Fields]),
Tpl = template(RevTag, Vsn),
io:format("Template = ~tp~n", [Tpl]),
NFields = gmser_chain_objects:deserialize(RevTag, Vsn, Tpl, Bytes),
io:format("NamedFields = ~tp~n", [NFields]),
deep_decode(RevTag, Vsn, Fields).
deep_decode(signed_tx, 1, [Sigs, Tx]) ->
[sig_valid(my_pubkey32(), Sig, Tx) || Sig <- Sigs],
just_the_tip_decode(Tx),
ok;
deep_decode(contract_create_tx, 1, _) ->
error(nyi);
deep_decode(_, _, _) ->
error(nyi).
sig_valid(Pk32, Sig, Tx) ->
io:format("Pk32 = ~tp~n", [Pk32]),
io:format("Sig = ~tp~n", [Sig]),
io:format("Tx = ~tp~n", [Tx]),
NetworkId = <<"groot.testnet">>,
{ok, Hash} = eblake2:blake2b(32, Tx),
Signatee = <<NetworkId/binary, Hash/binary>>,
io:format("NetworkId = ~tp~n", [NetworkId]),
io:format("Hash = ~tp~n", [Hash]),
io:format("Signatee = ~tp~n", [Signatee]),
SigValid = ecu_eddsa:sign_verify_detached(Sig, Signatee, Pk32),
io:format("SigValid = ~tp~n", [SigValid]),
ok.
% hacky to get fund via faucet to work
rq_grids(URL) ->
{ok, {{sign, https}, URL_II}} = hz_grids:parse(URL),
case httpc:request(URL_II) of
{ok, {{_HttpVsn, 200, _Slogan}, _Hdrs, JSONBodyStr}} ->
rq_grids_ii00(URL_II, JSONBodyStr);
{ok, {{_HttpVsn, 404, _Slogan}, _Hdrs, _JSONBodyStr}} ->
io:format("BUTTER YOUR OWN BISCUIT FATASS~n", []),
io:format("(404... probably faucet copypasta expired)~n", []);
Error ->
io:format("ERROR: ~tp~n", [Error])
end.
rq_grids_ii00(URL_II, JSONBodyStr) ->
{ok, Decoded} = zj:decode(JSONBodyStr),
io:format("Decoded = ~tp~n", [Decoded]),
case Decoded of
#{"chain" := "gajumaru",
"grids" := 1,
"network_id" := "groot.testnet",
"payload" := PayloadStr,
"public_id" := false,
"signature" := undefined,
"type" := "message"} -> rq_grids_iii(URL_II, Decoded, PayloadStr);
_Nah -> error(nah_brah)
end.
rq_grids_iii(URL_II, Decoded, PayloadStr) ->
io:format("PayloadStr = ~tp~n", [PayloadStr]),
Sig = base64:encode(hz:sign_message(unicode:characters_to_binary(PayloadStr), my_secret_key())),
Recoded = Decoded#{"signature" => Sig,
"public_id" => my_akstr()},
io:format("Recoded = ~tp~n", [Recoded]),
Reconfuckulated = zj:encode(Recoded),
io:format("Reconfuckulated = ~tp~n", [Reconfuckulated]),
Shit = httpc:request(post, {URL_II, [], "application/json", Reconfuckulated}, [], []),
io:format("Shit = ~tp~n", [Shit]).
% ok.
rq_my(everything) ->
rq_my(akstr),
rq_my(rbx_tuples);
rq_my(acc) ->
Ak = my_akstr(),
rq_query([Ak]);
rq_my(balance) ->
Ak = my_akstr(),
{ok, #{"balance" := B}} = hz:acc(Ak),
io:format("Balance = ~tp~n", [B]),
io:format("log10(Balance) = ~tp~n", [math:log10(B)]);
rq_my(akstr) ->
io:format("~ts~n", [my_akstr()]);
rq_my(url) ->
io:format("https://groot.testnet.gajumaru.io/account/~ts~n", [my_akstr()]);
rq_my(rbx_tuples) ->
io:format("~tp~n", [my_rbx_tuples()]).
rq_query([Ak = "ak_" ++ _ | Rest]) ->
{ok, X} = hz:acc(Ak),
io:format("~tp~n", [X]),
rq_query(Rest);
rq_query([Ct = "ct_" ++ _ | Rest]) ->
{ok, X} = hz:contract(Ct),
io:format("~tp~n", [X]),
rq_query(Rest);
rq_query([ThStr = "th" ++ _ | Rest]) ->
io:format("ThStr = ~tp~n", [ThStr]),
io:format("ExplorerUrl = ~tp~n", ["null"]),
{ok, TxInfo} = hz:tx_info(ThStr),
#{"call_info" := #{"return_value" := CbStr}} = TxInfo,
rq_decode(CbStr),
rq_query(Rest);
rq_query([]) ->
ok.
rq_src(["names.aes"]) ->
{ok, Bytes} = file:read_file(nct_filepath()),
io:format("~s", [Bytes]);
rq_src(["rubicon.aes"]) ->
{ok, Bytes} = file:read_file(nct_filepath()),
io:format("~s", [Bytes]);
rq_src(FailArgs) ->
badargs(FailArgs).
badargs(FailArgs) ->
io:format("you are a failure: ~p~n", [FailArgs]),
rq_help([]),
error(badargs).
rq_help(_) ->
io:format("~ts~n", [help_screen()]).
rq_debug(_) ->
ok = dbg_hz(),
ok = dbg_my(),
ok = dbg_nct(),
ok = dbg_rct(),
ok.
dbg_hz() ->
io:format("hz status: ~tp~n", [hz:status()]).
dbg_my() ->
Format =
"""
MY:
akstr ~ts~n
""",
Args = [my_akstr()],
io:format(Format, Args).
dbg_nct() ->
Format =
"""
NAMES CT:
filepath ~tp
ctstr ~ts
aaci ~tp~n
""",
Args =
[ nct_filepath()
, nct_ctstr()
, nct_aaci()
],
io:format(Format, Args).
dbg_rct() ->
Format =
"""
RUBICON CT:
filepath ~tp
aaci ~tp~n
""",
Args =
[ rct_filepath()
, rct_aaci()
],
io:format(Format, Args).
rq_register(Name) ->
ct_call(nct_aaci(), nct_ctstr(), "register", [Name]).
rq_lookup(Name) ->
ct_call(nct_aaci(), nct_ctstr(), "lookup", [Name]).
ct_call(AACI, CtStr, FunName, ErlArgs) ->
{ok, CallTx} = hz:contract_call(my_akstr(), AACI, CtStr, FunName, {erlang, ErlArgs}),
SignedTx = hz:sign_tx(CallTx, my_secret_key()),
{ok, PostResult} = hz:post_tx(SignedTx),
case PostResult of
#{"tx_hash" := ThStr} ->
rq_query(ThStr);
#{"error" := NodeErr} ->
io:format("ERROR: node sent back the following extremely helpful error message~n", []),
io:format("NodeErr = ~tp~n", [NodeErr]),
error(NodeErr);
_ ->
io:format("ERROR (nyi) node sent back voodoo:~n", []),
io:format("PostResult = ~tp~n", [PostResult]),
error(nyi)
end.
%% name contract deployed address
nct_ctstr() ->
"ct_2CDom5hKimtKSsTykhBYBKMgGv7youakZPVgk5h9qgNL3P65oV".
nct_aaci() ->
case hz:prepare_contract(nct_filepath()) of
{ok, AACI} -> AACI;
{error, E} -> error(E)
end.
nct_filepath() ->
ct_dir() ++ "/names.aes".
%% rubicon contract deployed address
rct_aaci() ->
case hz:prepare_contract(rct_filepath()) of
{ok, AACI} -> AACI;
{error, E} -> error(E)
end.
rct_filepath() ->
ct_dir() ++ "/rubicon.aes".
ct_dir() ->
zx:get_home() ++ "/ct".
%% my keys
% "ak_2pr2BL1jK625Uedt888p3YKRUEk3GDTo5r2zKW6gMaZePQP5ou"
my_akstr() ->
unicode:characters_to_list(gmser_api_encoder:encode(account_pubkey, my_pubkey32())).
my_pubkey32() ->
maps:get(public, my_keypair()).
my_secret_key() ->
maps:get(secret, my_keypair()).
my_keypair() ->
ecu_eddsa:sign_seed_keypair(my_seed()).
my_seed() ->
crypto:hash(sha3_256, <<"p@ssw0rd">>).
my_rbx_tuples() ->
NoiseStr = "Noise_NK_25519_ChaChaPoly_BLAKE2b",
ServicePubkey = <<116, 159, 91, 248, 138, 250, 73, 40, 231, 50,
81, 110, 137, 163, 44, 76, 48, 130, 225, 95,
168, 121, 93, 44, 148, 42, 180, 103, 11, 40,
168, 96>>,
ServiceAkStr = gmser_api_encoder:encode(account_pubkey, ServicePubkey),
NoisePrologue = <<>>,
[{"127.0.0.1", 6969, "tcp", NoiseStr, ServiceAkStr, NoisePrologue}].
%% hz setup
hz() ->
ok = application:ensure_started(hakuzaru),
ok = hz:tls(false),
ok = hz:chain_nodes(hz_nodes()).
hz_nodes() ->
[ {"tsuriai.jp", 4013}
, {"groot.testnet.gajumaru.io", 3013}
].
%% deploying
deploy_rct_with(RbxTuples) ->
case hz:contract_create(my_akstr(), rct_filepath(), [RbxTuples]) of
{ok, CreateTx} ->
deploy_rct_II(CreateTx);
Error ->
io:format("you are a failure: ~tp~n", [Error]),
error(Error)
end.
deploy_rct_II(UnsignedCreateTx) ->
SecretKey = my_secret_key(),
case hz:sign_tx(UnsignedCreateTx, SecretKey) of
{ok, SignedTx} ->
deploy_rct_III(SignedTx);
Error ->
io:format("you are a failure II: ~tp~n", [Error]),
error(Error)
end.
deploy_rct_III(SignedCreateTx) ->
case hz:post_tx(SignedCreateTx) of
{ok, Result_III} ->
io:format("Result the third: ~tp~n", [Result_III]);
Error ->
io:format("you are a failure III: ~tp~n", [Error]),
error(Error)
end.
%% totally not copypasted from gmser_chain_objects.erl
rev_tag(10) -> account;
rev_tag(11) -> signed_tx;
rev_tag(12) -> spend_tx;
rev_tag(13) -> data_extend_tx;
rev_tag(30) -> name;
rev_tag(31) -> name_commitment;
rev_tag(32) -> name_claim_tx;
rev_tag(33) -> name_preclaim_tx;
rev_tag(34) -> name_update_tx;
rev_tag(35) -> name_revoke_tx;
rev_tag(36) -> name_transfer_tx;
rev_tag(37) -> name_auction;
rev_tag(40) -> contract;
rev_tag(41) -> contract_call;
rev_tag(42) -> contract_create_tx;
rev_tag(43) -> contract_call_tx;
rev_tag(44) -> contract_source;
rev_tag(50) -> channel_create_tx;
rev_tag(501) -> channel_set_delegates_tx;
rev_tag(502) -> channel_delegates;
rev_tag(51) -> channel_deposit_tx;
rev_tag(52) -> channel_withdraw_tx;
rev_tag(521) -> channel_force_progress_tx;
rev_tag(53) -> channel_close_mutual_tx;
rev_tag(54) -> channel_close_solo_tx;
rev_tag(55) -> channel_slash_tx;
rev_tag(56) -> channel_settle_tx;
rev_tag(57) -> channel_offchain_tx;
rev_tag(570) -> channel_offchain_update_transfer;
rev_tag(571) -> channel_offchain_update_deposit;
rev_tag(572) -> channel_offchain_update_withdraw;
rev_tag(573) -> channel_offchain_update_create_contract;
rev_tag(574) -> channel_offchain_update_call_contract;
rev_tag(576) -> channel_offchain_update_meta;
rev_tag(575) -> channel_client_reconnect_tx;
rev_tag(58) -> channel;
rev_tag(59) -> channel_snapshot_solo_tx;
rev_tag(60) -> trees_poi;
rev_tag(61) -> trees_db;
rev_tag(62) -> state_trees;
rev_tag(63) -> mtree;
rev_tag(64) -> mtree_value;
rev_tag(621) -> contracts_mtree;
rev_tag(622) -> calls_mtree;
rev_tag(623) -> channels_mtree;
rev_tag(624) -> nameservice_mtree;
rev_tag(626) -> accounts_mtree;
rev_tag(627) -> acs_mtree;
rev_tag(628) -> entries_mtree;
rev_tag(70) -> compiler_sophia;
rev_tag(80) -> ga_attach_tx;
rev_tag(81) -> ga_meta_tx;
rev_tag(82) -> paying_for_tx;
rev_tag(810) -> ga_meta_tx_auth_data;
rev_tag(90) -> associate_chain;
rev_tag(91) -> ac_state;
rev_tag(92) -> ac_burn;
rev_tag(93) -> ac_create_tx;
rev_tag(94) -> ac_deposit_tx;
rev_tag(95) -> ac_update_cops_tx;
rev_tag(96) -> ac_rollup_tx;
rev_tag(97) -> ac_proposal_tx;
rev_tag(100) -> key_block;
rev_tag(101) -> micro_block;
rev_tag(102) -> light_micro_block;
rev_tag(110) -> testimony;
rev_tag(111) -> testimony_tx;
rev_tag(120) -> nt_native_token;
rev_tag(121) -> nt_create_tx;
rev_tag(122) -> nt_mint_tx;
rev_tag(123) -> nt_finalize_tx;
rev_tag(124) -> nt_trade_tx;
rev_tag(125) -> nt_burn_tx;
rev_tag(140) -> entry;
rev_tag(141) -> entry_create_tx;
rev_tag(142) -> entry_transfer_tx;
rev_tag(143) -> entry_destroy_tx;
rev_tag(200) -> pof.
tag(account) -> 10;
tag(signed_tx) -> 11;
tag(spend_tx) -> 12;
tag(data_extend_tx) -> 13;
tag(name) -> 30;
tag(name_commitment) -> 31;
tag(name_claim_tx) -> 32;
tag(name_preclaim_tx) -> 33;
tag(name_update_tx) -> 34;
tag(name_revoke_tx) -> 35;
tag(name_transfer_tx) -> 36;
tag(name_auction) -> 37;
tag(contract) -> 40;
tag(contract_call) -> 41;
tag(contract_create_tx) -> 42;
tag(contract_call_tx) -> 43;
tag(contract_source) -> 44;
tag(channel_create_tx) -> 50;
tag(channel_set_delegates_tx) -> 501;
tag(channel_delegates) -> 502;
tag(channel_deposit_tx) -> 51;
tag(channel_withdraw_tx) -> 52;
tag(channel_force_progress_tx) -> 521;
tag(channel_close_mutual_tx) -> 53;
tag(channel_close_solo_tx) -> 54;
tag(channel_slash_tx) -> 55;
tag(channel_settle_tx) -> 56;
tag(channel_offchain_tx) -> 57;
tag(channel_offchain_update_transfer) -> 570;
tag(channel_offchain_update_deposit) -> 571;
tag(channel_offchain_update_withdraw) -> 572;
tag(channel_offchain_update_create_contract) -> 573;
tag(channel_offchain_update_call_contract) -> 574;
tag(channel_offchain_update_meta) -> 576;
tag(channel_client_reconnect_tx) -> 575;
tag(channel) -> 58;
tag(channel_snapshot_solo_tx) -> 59;
tag(trees_poi) -> 60;
tag(trees_db) -> 61;
tag(state_trees) -> 62;
tag(mtree) -> 63;
tag(mtree_value) -> 64;
tag(contracts_mtree) -> 621;
tag(calls_mtree) -> 622;
tag(channels_mtree) -> 623;
tag(nameservice_mtree) -> 624;
tag(accounts_mtree) -> 626;
tag(acs_mtree) -> 627;
tag(entries_mtree) -> 628;
tag(compiler_sophia) -> 70;
tag(ga_attach_tx) -> 80;
tag(ga_meta_tx) -> 81;
tag(paying_for_tx) -> 82;
tag(ga_meta_tx_auth_data) -> 810;
tag(associate_chain) -> 90;
tag(ac_state) -> 91;
tag(ac_burn) -> 92;
tag(ac_create_tx) -> 93;
tag(ac_deposit_tx) -> 94;
tag(ac_update_cops_tx) -> 95;
tag(ac_rollup_tx) -> 96;
tag(ac_proposal_tx) -> 97;
tag(key_block) -> 100;
tag(micro_block) -> 101;
tag(light_micro_block) -> 102;
tag(testimony) -> 110;
tag(testimony_tx) -> 111;
tag(nt_native_token) -> 120;
tag(nt_create_tx) -> 121;
tag(nt_mint_tx) -> 122;
tag(nt_finalize_tx) -> 123;
tag(nt_trade_tx) -> 124;
tag(nt_burn_tx) -> 125;
tag(entry) -> 140;
tag(entry_create_tx) -> 141;
tag(entry_transfer_tx) -> 142;
tag(entry_destroy_tx) -> 143;
tag(pof) -> 200.
template(signed_tx, 1) ->
[{sigs, [binary]},
{tx, binary}];
template(contract_create_tx, 1) ->
[{owner_id, id},
{nonce, int},
{code, binary},
{source, binary},
{ct_version, int},
{ttl, int},
{deposit, int},
{amount, int},
{gas_price, int},
{gas, int},
{call_data, binary}];
template(_, _) ->
error(nyi).