ported of send_coins.js / SendFunds() to C++, incl initial block weight support (previous commit already updated fee); set a few 'rct' lookups from get() to get_optional() in serial bridge (likely bugfix for pre rct outs - probably was the cause of some exceptions); returning tx and txBlob_byteLength from convenience__create_transaction for send fns; removed now unnecessary calculate_fee, estimate_rct_tx_size, create_transaction from bridge (encapsulated in send routine).. switched estimated_tx_network_fee over to fee_per_b (API change - the app-side bridge will take fee_per_kb with optl fee_per_b, preferring the latter and converting the former to b by /= 1024

pull/29/head
Paul Shapiro 6 years ago
parent bb9e55d4fe
commit a9daf4cef9

@ -52,15 +52,9 @@ uint32_t monero_fee_utils::default_priority()
}
//
uint64_t monero_fee_utils::get_base_fee( // added as of v8
uint64_t fee_per_kb,
use_fork_rules_fn_type use_fork_rules_fn
uint64_t fee_per_b
) {
// since this is the lightwallet…
if (use_fork_rules_fn(HF_VERSION_PER_BYTE_FEE, 0/*default*/)) {
return fee_per_kb / 1024;
} else {
return fee_per_kb;
}
return fee_per_b;
}
uint64_t monero_fee_utils::get_fee_quantization_mask(
use_fork_rules_fn_type use_fork_rules_fn
@ -80,15 +74,14 @@ uint64_t monero_fee_utils::get_fee_quantization_mask(
}
//
uint64_t monero_fee_utils::estimated_tx_network_fee(
uint64_t fee_per_kb,
uint64_t base_fee,
uint32_t priority,
use_fork_rules_fn_type use_fork_rules_fn
) {
bool bulletproof = use_fork_rules_fn(get_bulletproof_fork(), 0); // eventually just hardcode this to true (?)
uint64_t fee_multiplier = get_fee_multiplier(priority, default_priority(), get_fee_algorithm(use_fork_rules_fn), use_fork_rules_fn);
std::vector<uint8_t> extra; // blank extra
size_t est_tx_size = estimate_rct_tx_size(2, fixed_mixinsize(), 2, extra.size(), bulletproof); // typically ~14kb post-rct, pre-bulletproofs
uint64_t estimated_fee = calculate_fee(fee_per_kb, est_tx_size, fee_multiplier);
size_t est_tx_size = estimate_rct_tx_size(2, fixed_mixinsize(), 2, extra.size(), true/*bulletproof*/); // typically ~14kb post-rct, pre-bulletproofs
uint64_t estimated_fee = calculate_fee_from_size(base_fee, est_tx_size, fee_multiplier);
//
return estimated_fee;
}
@ -246,7 +239,7 @@ uint64_t monero_fee_utils::estimate_fee(bool use_per_byte_fee, bool use_rct, int
else
{
const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof);
return calculate_fee(base_fee, estimated_tx_size, fee_multiplier);
return calculate_fee_from_size(base_fee, estimated_tx_size, fee_multiplier);
}
}
//
@ -256,22 +249,20 @@ uint64_t monero_fee_utils::calculate_fee_from_weight(uint64_t base_fee, uint64_t
fee = (fee + fee_quantization_mask - 1) / fee_quantization_mask * fee_quantization_mask;
return fee;
}
uint64_t monero_fee_utils::calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, uint64_t fee_multiplier)
{
return calculate_fee(fee_per_kb, blob.size(), fee_multiplier);
}
uint64_t monero_fee_utils::calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask)
{
if (use_per_byte_fee) {
return calculate_fee_from_weight(base_fee, cryptonote::get_transaction_weight(tx, blob_size), fee_multiplier, fee_quantization_mask);
} else {
return calculate_fee(base_fee, blob_size, fee_multiplier);
return calculate_fee_from_size(base_fee, blob_size, fee_multiplier);
}
}
uint64_t monero_fee_utils::calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier)
//
//uint64_t monero_fee_utils::calculate_fee_from_size(uint64_t fee_per_b, const cryptonote::blobdata &blob, uint64_t fee_multiplier)
//{
// return calculate_fee_from_size(fee_per_b, blob.size(), fee_multiplier);
//}
uint64_t monero_fee_utils::calculate_fee_from_size(uint64_t fee_per_b, size_t bytes, uint64_t fee_multiplier)
{
uint64_t kB = (bytes + 1023) / 1024;
//
return kB * fee_per_kb * fee_multiplier;
return bytes * fee_per_b * fee_multiplier;
}

@ -55,21 +55,21 @@ namespace monero_fee_utils
uint64_t get_upper_transaction_weight_limit(uint64_t upper_transaction_weight_limit__or_0_for_default, use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_fee_multiplier(uint32_t priority, uint32_t default_priority, int fee_algorithm, use_fork_rules_fn_type use_fork_rules_fn);
int get_fee_algorithm(use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_base_fee(uint64_t fee_per_kb, use_fork_rules_fn_type use_fork_rules_fn);
uint64_t get_base_fee(uint64_t fee_per_b);
uint64_t get_fee_quantization_mask(use_fork_rules_fn_type use_fork_rules_fn);
//
uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask);
//
uint64_t calculate_fee_from_weight(uint64_t base_fee, uint64_t weight, uint64_t fee_multiplier, uint64_t fee_quantization_mask);
uint64_t calculate_fee(uint64_t fee_per_kb, size_t bytes, uint64_t fee_multiplier);
uint64_t calculate_fee(uint64_t fee_per_kb, const blobdata &blob, uint64_t fee_multiplier);
uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask);
//
/*Added*/ uint64_t calculate_fee_from_size(uint64_t fee_per_b, size_t bytes, uint64_t fee_multiplier);
//
size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof);
uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof);
size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof);
uint64_t estimated_tx_network_fee( // convenience function for size + calc
uint64_t fee_per_kb,
uint64_t fee_per_b,
uint32_t priority, // when priority=0, falls back to monero_fee_utils::default_priority()
use_fork_rules_fn_type use_fork_rules_fn // this is extracted to a function so that implementations can optionally query the daemon (although this presently implies that such a call remains blocking)
);

@ -37,21 +37,8 @@
//
using namespace monero_fork_rules;
//
uint8_t monero_fork_rules::get_bulletproof_fork()
{ // the fork at which bulletproofs became optional
return 8;
}
bool monero_fork_rules::lightwallet_hardeded__use_bulletproofs()
{
return true; // This is temporary until we have the fork rules supplied by the server
}
//
bool monero_fork_rules::lightwallet_hardcoded__use_fork_rules(uint8_t version, int64_t early_blocks)
{
bool bulletproof = lightwallet_hardeded__use_bulletproofs();
if (version >= monero_fork_rules::get_bulletproof_fork()) {
return bulletproof;
}
return true; // TODO - we don't have the actual fork rules from thje lightwallet server yet
//
// full wallets do:
@ -76,5 +63,9 @@ uint32_t monero_fork_rules::fixed_ringsize()
}
uint32_t monero_fork_rules::fixed_mixinsize()
{
return monero_fork_rules::fixed_ringsize() - 1;
return fixed_ringsize() - 1;
}
uint64_t monero_fork_rules::dust_threshold()
{
return 2000000000;
}

@ -42,14 +42,12 @@ namespace monero_fork_rules
{
typedef std::function<bool(uint8_t/*version*/, int64_t/*early_blocks*/)> use_fork_rules_fn_type;
//
uint8_t get_bulletproof_fork();
bool lightwallet_hardeded__use_bulletproofs();
//
bool lightwallet_hardcoded__use_fork_rules(uint8_t version, int64_t early_blocks); // convenience
//
bool lightwallet_hardcoded__use_fork_rules(uint8_t version, int64_t early_blocks); // convenience - to be called by a use_fork_rules_fn_type implementation
//
uint32_t fixed_ringsize(); // not mixinsize, which would be ringsize-1
uint32_t fixed_mixinsize(); // not ringsize, which would be mixinsize+1
//
uint64_t dust_threshold();
}
#endif /* monero_fork_rules */

@ -91,7 +91,37 @@ bool monero_transfer_utils::is_tx_spendtime_unlocked(
return false;
}
//
// Constructing transactions
CreateTransactionErrorCode _add_pid_to_tx_extra(
optional<string> payment_id_string,
vector<uint8_t> &extra
) { // Detect hash8 or hash32 char hex string as pid and configure 'extra' accordingly
bool r = false;
if (payment_id_string != none) {
crypto::hash payment_id;
r = monero_paymentID_utils::parse_long_payment_id(*payment_id_string, payment_id);
if (r) {
std::string extra_nonce;
cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
return couldntAddPIDNonceToTXExtra;
}
} else {
crypto::hash8 payment_id8;
r = monero_paymentID_utils::parse_short_payment_id(*payment_id_string, payment_id8);
if (!r) { // a PID has been specified by the user but the last resort in validating it fails; error
return invalidPID;
}
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
return couldntAddPIDNonceToTXExtra;
}
}
}
return noError;
}
bool _rct_hex_to_rct_commit(
const std::string &rct_string,
rct::key &rct_commit
@ -140,6 +170,266 @@ bool _verify_sec_key(const crypto::secret_key &secret_key, const crypto::public_
return r && public_key == calculated_pub;
}
//
//
//----------------------------------------------------------------------------------------------------
namespace
{
template<typename T>
T pop_index(std::vector<T>& vec, size_t idx)
{
CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty");
CHECK_AND_ASSERT_MES(idx < vec.size(), T(), "idx out of bounds");
T res = vec[idx];
if (idx + 1 != vec.size()) {
vec[idx] = vec.back();
}
vec.resize(vec.size() - 1);
return res;
}
//
template<typename T>
T pop_random_value(std::vector<T>& vec)
{
CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty");
size_t idx = crypto::rand<size_t>() % vec.size();
return pop_index (vec, idx);
}
}
//
//
// Decomposed Send procedure
void monero_transfer_utils::send_step1__prepare_params_for_get_decoys(
Send_Step1_RetVals &retVals,
//
optional<string> payment_id_string,
uint64_t sending_amount,
bool is_sweeping,
uint32_t simple_priority,
use_fork_rules_fn_type use_fork_rules_fn,
//
const vector<SpendableOutput> &unspent_outs,
uint64_t fee_per_b, // per v8
//
optional<uint64_t> passedIn_attemptAt_fee
) {
retVals = {};
//
if (is_sweeping) {
if (sending_amount != 0 && sending_amount != UINT64_MAX) {
THROW_WALLET_EXCEPTION_IF(
sending_amount != 0 && sending_amount != UINT64_MAX,
error::wallet_internal_error, "Ambiguous arguments; Pass sending_amount 0 while sweeping"
);
return;
}
} else { // not sweeping
if (sending_amount == 0) {
retVals.errCode = enteredAmountTooLow;
return;
}
}
//
uint32_t fake_outs_count = monero_fork_rules::fixed_mixinsize();
retVals.mixin = fake_outs_count;
//
bool use_rct = true;
bool bulletproof = true;
//
std::vector<uint8_t> extra;
CreateTransactionErrorCode tx_extra__code = _add_pid_to_tx_extra(payment_id_string, extra);
if (tx_extra__code != noError) {
retVals.errCode = tx_extra__code;
return;
}
const uint64_t base_fee = get_base_fee(fee_per_b); // in other words, fee_per_b
const uint64_t fee_multiplier = get_fee_multiplier(simple_priority, default_priority(), get_fee_algorithm(use_fork_rules_fn), use_fork_rules_fn);
const uint64_t fee_quantization_mask = get_fee_quantization_mask(use_fork_rules_fn);
//
uint64_t attempt_at_min_fee;
if (passedIn_attemptAt_fee == none) {
attempt_at_min_fee = estimate_fee(true/*use_per_byte_fee*/, true/*use_rct*/, 2/*est num inputs*/, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
// opted to do this instead of `const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof));`
// TODO: estimate with 1 input or 2?
} else {
attempt_at_min_fee = *passedIn_attemptAt_fee;
}
struct Total
{
static uint64_t with(uint64_t sending_amount, uint64_t fee_amount)
{
return sending_amount + fee_amount;
}
};
// fee may get changed as follows…
uint64_t potential_total; // aka balance_required
if (is_sweeping) {
potential_total = UINT64_MAX; // balance required: all
} else {
potential_total = Total::with(sending_amount, attempt_at_min_fee);
}
//
// Gather outputs and amount to use for getting decoy outputs…
uint64_t using_outs_amount = 0;
vector<SpendableOutput> remaining_unusedOuts = unspent_outs; // take copy so not to modify original
// TODO: factor this out to get spendable balance for display in the MM wallet:
while (using_outs_amount < potential_total && remaining_unusedOuts.size() > 0) {
auto out = pop_random_value(remaining_unusedOuts);
if (!use_rct && out.rct != none) {
// out.rct is set by the server
continue; // skip rct outputs if not creating rct tx
}
if (out.amount < monero_fork_rules::dust_threshold()) { // amount is dusty..
if (!is_sweeping) {
cout << "Not sweeping, and found a dusty (though maybe mixable) output... skipping it!" << endl;
continue;
}
if (out.rct == none) { // Sweeping, and found a dusty but unmixable (non-rct) output... skipping it!
cout << "Sweeping, and found a dusty but unmixable (non-rct) output... skipping it!" << endl;
continue;
} else {
cout << "Sweeping and found a dusty but mixable (rct) amount... keeping it!" << endl;
}
}
retVals.using_outs.push_back(out);
using_outs_amount += out.amount;
cout << "Using output: " << out.amount << " - " << out.public_key << endl;
}
retVals.spendable_balance = using_outs_amount; // must store for needMoreMoneyThanFound return
// Note: using_outs and using_outs_amount may still get modified below (so retVals.spendable_balance gets updated)
//
// if (/*using_outs.size() > 1*/ && use_rct) { // FIXME? see original core js
uint64_t needed_fee = estimate_fee(
true/*use_per_byte_fee*/, use_rct,
retVals.using_outs.size(), fake_outs_count, /*tx.dsts.size()*/1+1, extra.size(),
bulletproof, base_fee, fee_multiplier, fee_quantization_mask
);
// if newNeededFee < neededFee, use neededFee instead (should only happen on the 2nd or later times through (due to estimated fee being too low))
if (needed_fee < attempt_at_min_fee) {
needed_fee = attempt_at_min_fee;
}
//
// NOTE: needed_fee may get further modified below when !is_sweeping if using_outs_amount < total_incl_fees and gets finalized (for this function's scope) as using_fee
//
retVals.required_balance = is_sweeping ? needed_fee : potential_total; // must store for needMoreMoneyThanFound return .... NOTE: this is set to needed_fee for is_sweeping because that's literally the required balance, which an caller may want to print in case they get needMoreMoneyThanFound - note this gets updated below when !is_sweeping
//
uint64_t total_wo_fee = is_sweeping
? /*now that we know outsAmount>needed_fee*/(using_outs_amount - needed_fee)
: sending_amount;
retVals.final_total_wo_fee = total_wo_fee;
//
uint64_t total_incl_fees;
if (is_sweeping) {
if (using_outs_amount < needed_fee) { // like checking if the result of the following total_wo_fee is < 0
retVals.errCode = needMoreMoneyThanFound; // sufficiently up-to-date (for this return case) required_balance and using_outs_amount (spendable balance) will have been stored for return by this point
return;
}
total_incl_fees = using_outs_amount;
} else {
total_incl_fees = sending_amount + needed_fee; // because fee changed because using_outs.size() was updated
while (using_outs_amount < total_incl_fees && remaining_unusedOuts.size() > 0) { // add outputs 1 at a time till we either have them all or can meet the fee
auto out = pop_random_value(remaining_unusedOuts);
cout << "Using output: " << out.amount << " - " << out.public_key << endl;
retVals.using_outs.push_back(out);
using_outs_amount += out.amount;
retVals.spendable_balance = using_outs_amount; // must store for needMoreMoneyThanFound return
//
// Recalculate fee, total incl fees
needed_fee = estimate_fee(
true/*use_per_byte_fee*/, use_rct,
retVals.using_outs.size(), fake_outs_count, /*tx.dsts.size()*/1+1, extra.size(),
bulletproof, base_fee, fee_multiplier, fee_quantization_mask
);
total_incl_fees = sending_amount + needed_fee; // because fee changed
}
retVals.required_balance = total_incl_fees; // update required_balance b/c total_incl_fees changed
}
retVals.using_fee = needed_fee;
//
cout << "Final attempt at fee: " << needed_fee << " for " << retVals.using_outs.size() << " inputs" << endl;
cout << "Balance to be used: " << total_incl_fees << endl;
if (using_outs_amount < total_incl_fees) {
retVals.errCode = needMoreMoneyThanFound; // sufficiently up-to-date (for this return case) required_balance and using_outs_amount (spendable balance) will have been stored for return by this point.
return;
}
//
// Change can now be calculated
uint64_t change_amount = 0; // to initialize
if (using_outs_amount > total_incl_fees) {
THROW_WALLET_EXCEPTION_IF(is_sweeping, error::wallet_internal_error, "Unexpected total_incl_fees > using_outs_amount while sweeping");
change_amount = using_outs_amount - total_incl_fees;
}
cout << "Calculated change amount:" << change_amount << endl;
retVals.change_amount = change_amount;
//
// uint64_t tx_estimated_weight = estimate_tx_weight(true/*use_rct*/, retVals.using_outs.size(), fake_outs_count, 1+1, extra.size(), true/*bulletproof*/);
// if (tx_estimated_weight >= TX_WEIGHT_TARGET(get_upper_transaction_weight_limit(0, use_fork_rules_fn))) {
// // TODO?
// }
}
void monero_transfer_utils::send_step2__reenterable_try_create_transaction(
Send_Step2_RetVals &retVals,
//
string from_address_string,
string sec_viewKey_string,
string sec_spendKey_string,
string to_address_string,
optional<string> payment_id_string,
uint64_t sending_amount,
uint64_t change_amount,
uint64_t fee_amount,
uint32_t simple_priority,
vector<SpendableOutput> &using_outs,
uint64_t fee_per_b, // per v8
vector<RandomAmountOutputs> &mix_outs,
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time, // or 0
cryptonote::network_type nettype
) {
retVals = {};
//
Convenience_TransactionConstruction_RetVals create_tx__retVals;
monero_transfer_utils::convenience__create_transaction(
create_tx__retVals,
from_address_string,
sec_viewKey_string, sec_spendKey_string,
to_address_string, payment_id_string,
sending_amount, change_amount, fee_amount,
using_outs, mix_outs,
use_fork_rules_fn,
unlock_time,
nettype // TODO: move to after from_address_string
);
if (create_tx__retVals.errCode != noError) {
retVals.errCode = create_tx__retVals.errCode;
return;
}
THROW_WALLET_EXCEPTION_IF(create_tx__retVals.signed_serialized_tx_string == boost::none, error::wallet_internal_error, "Not expecting no signed_serialized_tx_string given no error");
//
size_t blob_size = *create_tx__retVals.txBlob_byteLength;
uint64_t fee_actually_needed = calculate_fee(
true/*use_per_byte_fee*/,
*create_tx__retVals.tx, blob_size,
get_base_fee(fee_per_b)/*i.e. fee_per_b*/,
get_fee_multiplier(simple_priority, default_priority(), get_fee_algorithm(use_fork_rules_fn), use_fork_rules_fn),
get_fee_quantization_mask(use_fork_rules_fn)
);
if (fee_actually_needed > fee_amount) {
cout << "Need to reconstruct tx with fee of at least " << fee_actually_needed << "." << endl;
retVals.tx_must_be_reconstructed = true;
retVals.fee_actually_needed = fee_actually_needed;
return;
}
retVals.signed_serialized_tx_string = std::move(*(create_tx__retVals.signed_serialized_tx_string));
retVals.tx_hash_string = std::move(*(create_tx__retVals.tx_hash_string));
retVals.tx_key_string = std::move(*(create_tx__retVals.tx_key_string));
}
//
//
// Underlying implementations to mimic historical JS-land create_transaction / construct_tx impls
//
void monero_transfer_utils::create_transaction(
TransactionConstruction_RetVals &retVals,
const account_keys& sender_account_keys, // this will reference a particular hw::device
@ -162,7 +452,7 @@ void monero_transfer_utils::create_transaction(
// TODO: do we need to sort destinations by amount, here, according to 'decompose_destinations'?
//
uint32_t fake_outputs_count = fixed_mixinsize();
bool bulletproof = use_fork_rules_fn(get_bulletproof_fork(), 0);
bool bulletproof = true;
const rct::RangeProofType range_proof_type = bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean;
//
if (mix_outs.size() != outputs.size() && fake_outputs_count != 0) {
@ -385,7 +675,6 @@ void monero_transfer_utils::create_transaction(
retVals.additional_tx_keys = additional_tx_keys;
}
//
//
void monero_transfer_utils::convenience__create_transaction(
Convenience_TransactionConstruction_RetVals &retVals,
const string &from_address_string,
@ -418,55 +707,45 @@ void monero_transfer_utils::convenience__create_transaction(
THROW_WALLET_EXCEPTION_IF(!string_tools::hex_to_pod(sec_spendKey_string, sec_spendKey), error::wallet_internal_error, "Couldn't parse spend key");
account_keys.m_spend_secret_key = sec_spendKey;
}
//
cryptonote::address_parse_info to_addr_info;
THROW_WALLET_EXCEPTION_IF(!cryptonote::get_account_address_from_str(to_addr_info, nettype, to_address_string), error::wallet_internal_error, "Couldn't parse to-address");
THROW_WALLET_EXCEPTION_IF(
to_address_string.find(".") != std::string::npos, // assumed to be an OA address asXMR addresses do not have periods and OA addrs must
error::wallet_internal_error,
"Integrators must resolve OA addresses before calling Send"
); // This would be an app code fault
cryptonote::address_parse_info to_addr_info; // just in case…
if (!cryptonote::get_account_address_from_str(to_addr_info, nettype, to_address_string)) {
retVals.errCode = couldntDecodeToAddress;
return;
}
//
std::vector<uint8_t> extra;
bool payment_id_seen = false;
{ // Detect hash8 or hash32 char hex string as pid and configure 'extra' accordingly
bool r = false;
if (payment_id_string != none) {
crypto::hash payment_id;
r = monero_paymentID_utils::parse_long_payment_id(*payment_id_string, payment_id);
if (r) {
std::string extra_nonce;
cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
} else {
crypto::hash8 payment_id8;
r = monero_paymentID_utils::parse_short_payment_id(*payment_id_string, payment_id8);
if (r) {
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8);
r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
}
}
payment_id_seen = true;
} else {
if (to_addr_info.is_subaddress && payment_id_seen) {
retVals.errCode = cantUsePIDWithSubAddress; // Never use a subaddress with a payment ID
return;
}
if (to_addr_info.has_payment_id) {
if (payment_id_seen) {
retVals.errCode = nonZeroPIDWithIntAddress; // can't use int addr at same time as supplying manual pid
return;
}
if (to_addr_info.is_subaddress) {
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Unexpected is_subaddress && has_payment_id"); // should never happen
return;
}
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, to_addr_info.payment_id);
bool r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
retVals.errCode = couldntSetPIDToTXExtra;
return;
}
payment_id_seen = true;
}
CreateTransactionErrorCode tx_extra__code = _add_pid_to_tx_extra(payment_id_string, extra);
if (tx_extra__code != noError) {
retVals.errCode = tx_extra__code;
return;
}
bool payment_id_seen = payment_id_string != none; // logically this is true since payment_id_string has passed validation (or we'd have errored)
if (to_addr_info.is_subaddress && payment_id_seen) {
retVals.errCode = cantUsePIDWithSubAddress; // Never use a subaddress with a payment ID
return;
}
if (to_addr_info.has_payment_id) {
if (payment_id_seen) {
retVals.errCode = nonZeroPIDWithIntAddress; // can't use int addr at same time as supplying manual pid
return;
}
if (to_addr_info.is_subaddress) {
THROW_WALLET_EXCEPTION_IF(false, error::wallet_internal_error, "Unexpected is_subaddress && has_payment_id"); // should never happen
return;
}
std::string extra_nonce;
cryptonote::set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, to_addr_info.payment_id);
bool r = cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce);
if (!r) {
retVals.errCode = couldntAddPIDNonceToTXExtra;
return;
}
payment_id_seen = true;
}
//
uint32_t subaddr_account_idx = 0;
@ -476,20 +755,13 @@ void monero_transfer_utils::convenience__create_transaction(
TransactionConstruction_RetVals actualCall_retVals;
create_transaction(
actualCall_retVals,
account_keys,
subaddr_account_idx,
subaddresses,
account_keys, subaddr_account_idx, subaddresses,
to_addr_info.address,
sending_amount,
change_amount,
fee_amount,
outputs,
mix_outs,
extra,
sending_amount, change_amount, fee_amount,
outputs, mix_outs,
extra, // TODO: move to after address
use_fork_rules_fn,
unlock_time,
true, // rct
nettype
unlock_time, true/*rct*/, nettype
);
if (actualCall_retVals.errCode != noError) {
retVals.errCode = actualCall_retVals.errCode; // pass-through
@ -514,4 +786,6 @@ void monero_transfer_utils::convenience__create_transaction(
}
}
retVals.tx_key_string = oss.str();
retVals.tx = *actualCall_retVals.tx; // for calculating block weight; FIXME: std::move?
retVals.txBlob_byteLength = txBlob_byteLength;
}

@ -94,22 +94,27 @@ namespace monero_transfer_utils
inputAmountOverflow = 6,
mixRCTOutsMissingCommit = 7,
resultFeeNotEqualToGiven = 8,
needMoreMoneyThanFound = 9,
invalidDestinationAddress = 10,
nonZeroPIDWithIntAddress = 11,
cantUsePIDWithSubAddress = 12,
couldntSetPIDToTXExtra = 13,
givenAnInvalidPubKey = 14,
invalidCommitOrMaskOnOutputRCT = 15,
transactionNotConstructed = 16,
transactionTooBig = 17,
notYetImplemented = 18
invalidDestinationAddress = 9,
nonZeroPIDWithIntAddress = 10,
cantUsePIDWithSubAddress = 11,
couldntAddPIDNonceToTXExtra = 12,
givenAnInvalidPubKey = 13,
invalidCommitOrMaskOnOutputRCT = 14,
transactionNotConstructed = 15,
transactionTooBig = 16,
notYetImplemented = 17,
couldntDecodeToAddress = 18,
invalidPID = 19,
enteredAmountTooLow = 20,
needMoreMoneyThanFound = 90
};
static inline string err_msg_from_err_code__create_transaction(CreateTransactionErrorCode code)
{
switch (code) {
case noError:
return "No error";
case couldntDecodeToAddress:
return "Couldn't decode address";
case noDestinations:
return "No destinations provided";
case wrongNumberOfMixOutsProvided:
@ -131,11 +136,11 @@ namespace monero_transfer_utils
case invalidDestinationAddress:
return "Invalid destination address";
case nonZeroPIDWithIntAddress:
return "Can't supply a PID with an integrated address";
return "Payment ID must be blank when using an integrated address";
case cantUsePIDWithSubAddress:
return "Can't use PID with subaddress";
case couldntSetPIDToTXExtra:
return "Couldn't set PID to tx extra";
return "Payment ID must be blank when using a subaddress";
case couldntAddPIDNonceToTXExtra:
return "Couldn't add nonce to tx extra";
case givenAnInvalidPubKey:
return "Invalid pub key";
case invalidCommitOrMaskOnOutputRCT:
@ -146,35 +151,86 @@ namespace monero_transfer_utils
return "Transaction too big";
case notYetImplemented:
return "Not yet implemented";
case invalidPID:
return "Invalid payment ID";
case enteredAmountTooLow:
return "The amount you've entered is too low";
}
}
struct TransactionConstruction_RetVals
//
// Send_Step* functions procedure for integrators:
// 1. call GetUnspentOuts endpoint
// 2. call step1__prepare_params_for_get_decoys to get params for calling RandomOuts; call GetRandomOuts
// 3. call step2__reenterable_… with retVals from Step1 (incl using_outs, RandomOuts)
// 3a. While tx must be reconstructed, re-call step1 passing step2 fee_actually_needed as passedIn_attemptAt_fee, then re-request RandomOuts again, and call step2 again
// 3b. If good tx constructed, proceed to submit/save the tx
// Note: This separation of steps fully encodes SendFunds_ProcessStep
//
struct Send_Step1_RetVals
{
CreateTransactionErrorCode errCode;
CreateTransactionErrorCode errCode; // if != noError, abort Send process
// for display / information purposes on errCode=needMoreMoneyThanFound during step1:
uint64_t spendable_balance; // (effectively but not the same as spendable_balance)
uint64_t required_balance; // for display / information purposes on errCode=needMoreMoneyThanFound during step1
//
optional<transaction> tx;
optional<secret_key> tx_key;
optional<vector<secret_key>> additional_tx_keys;
// Success case return values
uint32_t mixin;
vector<SpendableOutput> using_outs;
uint64_t using_fee;
uint64_t final_total_wo_fee;
uint64_t change_amount;
};
// TODO: add priority
void create_transaction(
TransactionConstruction_RetVals &retVals,
const account_keys& sender_account_keys, // this will reference a particular hw::device
const uint32_t subaddr_account_idx, // pass 0 for no subaddrs
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
const account_public_address &to_addr,
void send_step1__prepare_params_for_get_decoys(
Send_Step1_RetVals &retVals,
//
optional<string> payment_id_string,
uint64_t sending_amount,
bool is_sweeping,
uint32_t simple_priority,
use_fork_rules_fn_type use_fork_rules_fn,
//
const vector<SpendableOutput> &unspent_outs,
uint64_t fee_per_b, // per v8
//
optional<uint64_t> passedIn_attemptAt_fee // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min
);
//
struct Send_Step2_RetVals
{
CreateTransactionErrorCode errCode; // if != noError, abort Send process
//
// Reconstruct-required parameters:
bool tx_must_be_reconstructed; // if true, re-request RandomOuts with the following parameters and retry step3
uint64_t fee_actually_needed; // will be non-zero if tx_must_be_reconstructed
//
// Success parameters:
optional<string> signed_serialized_tx_string;
optional<string> tx_hash_string;
optional<string> tx_key_string; // this includes additional_tx_keys
};
void send_step2__reenterable_try_create_transaction(
Send_Step2_RetVals &retVals,
//
string from_address_string,
string sec_viewKey_string,
string sec_spendKey_string,
string to_address_string,
optional<string> payment_id_string,
uint64_t sending_amount,
uint64_t change_amount,
uint64_t fee_amount,
vector<SpendableOutput> &outputs,
uint32_t simple_priority,
vector<SpendableOutput> &using_outs,
uint64_t fee_per_b, // per v8
vector<RandomAmountOutputs> &mix_outs,
std::vector<uint8_t> &extra, // this is not declared const b/c it may have the output tx pub key appended to it
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time = 0, // or 0
bool rct = true,
network_type nettype = MAINNET
uint64_t unlock_time, // or 0
cryptonote::network_type nettype
);
//
//
// Lower level functions - generally you won't need to call these (these are what used to live in cn_utils.js)
//
struct Convenience_TransactionConstruction_RetVals
{
CreateTransactionErrorCode errCode;
@ -182,6 +238,8 @@ namespace monero_transfer_utils
optional<string> signed_serialized_tx_string;
optional<string> tx_hash_string;
optional<string> tx_key_string; // this includes additional_tx_keys
optional<transaction> tx; // for block weight
optional<size_t> txBlob_byteLength;
};
void convenience__create_transaction(
Convenience_TransactionConstruction_RetVals &retVals,
@ -199,6 +257,31 @@ namespace monero_transfer_utils
uint64_t unlock_time = 0, // or 0
network_type nettype = MAINNET
);
struct TransactionConstruction_RetVals
{
CreateTransactionErrorCode errCode;
//
optional<transaction> tx;
optional<secret_key> tx_key;
optional<vector<secret_key>> additional_tx_keys;
};
void create_transaction(
TransactionConstruction_RetVals &retVals,
const account_keys& sender_account_keys, // this will reference a particular hw::device
const uint32_t subaddr_account_idx, // pass 0 for no subaddrs
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses,
const account_public_address &to_addr,
uint64_t sending_amount,
uint64_t change_amount,
uint64_t fee_amount,
vector<SpendableOutput> &outputs,
vector<RandomAmountOutputs> &mix_outs,
std::vector<uint8_t> &extra, // this is not declared const b/c it may have the output tx pub key appended to it
use_fork_rules_fn_type use_fork_rules_fn,
uint64_t unlock_time = 0, // or 0
bool rct = true,
network_type nettype = MAINNET
);
}
#endif /* monero_transfer_utils_hpp */

@ -89,6 +89,21 @@ string serial_bridge::string_from_nettype(network_type nettype)
return "UNDEFINED";
}
}
struct RetVals_Transforms
{
static string str_from(uint64_t v)
{
std::ostringstream o;
o << v;
return o.str();
}
static string str_from(uint32_t v)
{
std::ostringstream o;
o << v;
return o.str();
}
};
//
// Shared - Parsing - Args
bool parsed_json_root(const string &args_string, boost::property_tree::ptree &json_root)
@ -379,49 +394,6 @@ string serial_bridge::validate_components_for_login(const string &args_string)
//
return ret_json_from_root(root);
}
//
string serial_bridge::estimate_rct_tx_size(const string &args_string)
{
boost::property_tree::ptree json_root;
if (!parsed_json_root(args_string, json_root)) {
// it will already have thrown an exception
return error_ret_json_from_message("Invalid JSON");
}
uint32_t size = monero_fee_utils::estimate_rct_tx_size(
json_root.get<int>("n_inputs"),
json_root.get<int>("mixin"),
json_root.get<int>("n_outputs"),
json_root.get<int>("extra_size"),
json_root.get<bool>("bulletproof")
);
std::ostringstream o;
o << size;
//
boost::property_tree::ptree root;
root.put(ret_json_key__generic_retVal(), o.str());
//
return ret_json_from_root(root);
}
string serial_bridge::calculate_fee(const string &args_string)
{
boost::property_tree::ptree json_root;
if (!parsed_json_root(args_string, json_root)) {
// it will already have thrown an exception
return error_ret_json_from_message("Invalid JSON");
}
uint64_t fee = monero_fee_utils::calculate_fee(
stoull(json_root.get<string>("fee_per_kb")),
stoul(json_root.get<string>("num_bytes")),
stoull(json_root.get<string>("fee_multiplier"))
);
std::ostringstream o;
o << fee;
//
boost::property_tree::ptree root;
root.put(ret_json_key__generic_retVal(), o.str());
//
return ret_json_from_root(root);
}
string serial_bridge::estimated_tx_network_fee(const string &args_string)
{
boost::property_tree::ptree json_root;
@ -430,7 +402,7 @@ string serial_bridge::estimated_tx_network_fee(const string &args_string)
return error_ret_json_from_message("Invalid JSON");
}
uint64_t fee = monero_fee_utils::estimated_tx_network_fee(
stoull(json_root.get<string>("fee_per_kb")),
stoull(json_root.get<string>("fee_per_b")),
stoul(json_root.get<string>("priority")),
[] (uint8_t version, int64_t early_blocks) -> bool
{
@ -484,31 +456,105 @@ string serial_bridge::generate_key_image(const string &args_string)
}
//
//
// TODO: probably take transaction secret key as an argument so we don't have to worry about randomness there
string serial_bridge::create_transaction(const string &args_string)
// TODO: possibly take tx sec key as a arg so we don't have to worry about randomness there
string serial_bridge::send_step1__prepare_params_for_get_decoys(const string &args_string)
{
boost::property_tree::ptree json_root;
if (!parsed_json_root(args_string, json_root)) {
// it will already have thrown an exception
return error_ret_json_from_message("Invalid JSON");
}
network_type nettype = nettype_from_string(json_root.get<string>("nettype_string"));
//
vector<SpendableOutput> outputs;
BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, json_root.get_child("outputs"))
vector<SpendableOutput> unspent_outs;
BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, json_root.get_child("unspent_outs"))
{
assert(output_desc.first.empty()); // array elements have no names
SpendableOutput out{};
out.amount = stoull(output_desc.second.get<string>("amount"));
out.public_key = output_desc.second.get<string>("public_key");
out.rct = output_desc.second.get<string>("rct");
out.rct = output_desc.second.get_optional<string>("rct");
out.global_index = stoull(output_desc.second.get<string>("global_index"));
out.index = stoull(output_desc.second.get<string>("index"));
out.tx_pub_key = output_desc.second.get<string>("tx_pub_key");
//
outputs.push_back(out);
unspent_outs.push_back(out);
}
optional<string> optl__passedIn_attemptAt_fee_string = json_root.get_optional<string>("passedIn_attemptAt_fee");
optional<uint64_t> optl__passedIn_attemptAt_fee = none;
if (optl__passedIn_attemptAt_fee_string != none) {
optl__passedIn_attemptAt_fee = stoull(*optl__passedIn_attemptAt_fee_string);
}
Send_Step1_RetVals retVals;
monero_transfer_utils::send_step1__prepare_params_for_get_decoys(
retVals,
//
json_root.get_optional<string>("payment_id_string"),
stoull(json_root.get<string>("sending_amount")),
json_root.get<bool>("is_sweeping"),
stoul(json_root.get<string>("priority")),
[] (uint8_t version, int64_t early_blocks) -> bool
{
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
},
unspent_outs,
stoull(json_root.get<string>("fee_per_b")), // per v8
//
optl__passedIn_attemptAt_fee // use this for passing step2 "must-reconstruct" return values back in, i.e. re-entry; when nil, defaults to attempt at network min
);
boost::property_tree::ptree root;
if (retVals.errCode != noError) {
root.put(ret_json_key__any__err_code(), retVals.errCode);
//
// The following will be set if errCode==needMoreMoneyThanFound - and i'm depending on them being 0 otherwise
root.put(ret_json_key__send__spendable_balance(), std::move(RetVals_Transforms::str_from(retVals.spendable_balance)));
root.put(ret_json_key__send__required_balance(), std::move(RetVals_Transforms::str_from(retVals.required_balance)));
} else {
root.put(ret_json_key__send__mixin(), std::move(RetVals_Transforms::str_from(retVals.mixin)));
root.put(ret_json_key__send__using_fee(), std::move(RetVals_Transforms::str_from(retVals.using_fee)));
root.put(ret_json_key__send__final_total_wo_fee(), std::move(RetVals_Transforms::str_from(retVals.final_total_wo_fee)));
root.put(ret_json_key__send__change_amount(), std::move(RetVals_Transforms::str_from(retVals.change_amount)));
{
boost::property_tree::ptree using_outs_ptree;
BOOST_FOREACH(SpendableOutput &out, retVals.using_outs)
{ // PROBABLY don't need to shuttle these back (could send only public_key) but consumers might like the feature of being able to send this JSON structure directly back to step2 without reconstructing it for themselves
boost::property_tree::ptree out_ptree;
out_ptree.put("amount", std::move(RetVals_Transforms::str_from(out.amount)));
out_ptree.put("public_key", out.public_key); // FIXME: no std::move correct?
if (out.rct != none) {
out_ptree.put("rct", *out.rct); // copy vs move ?
}
out_ptree.put("global_index", std::move(RetVals_Transforms::str_from(out.global_index)));
out_ptree.put("index", std::move(RetVals_Transforms::str_from(out.index)));
out_ptree.put("tx_pub_key", out.tx_pub_key);
using_outs_ptree.push_back(std::make_pair("", out_ptree));
}
root.add_child(ret_json_key__send__using_outs(), using_outs_ptree);
}
}
return ret_json_from_root(root);
}
string serial_bridge::send_step2__reenterable_try_create_transaction(const string &args_string)
{
boost::property_tree::ptree json_root;
if (!parsed_json_root(args_string, json_root)) {
// it will already have thrown an exception
return error_ret_json_from_message("Invalid JSON");
}
//
vector<SpendableOutput> using_outs;
BOOST_FOREACH(boost::property_tree::ptree::value_type &output_desc, json_root.get_child("using_outs"))
{
assert(output_desc.first.empty()); // array elements have no names
SpendableOutput out{};
out.amount = stoull(output_desc.second.get<string>("amount"));
out.public_key = output_desc.second.get<string>("public_key");
out.rct = output_desc.second.get_optional<string>("rct");
out.global_index = stoull(output_desc.second.get<string>("global_index"));
out.index = stoull(output_desc.second.get<string>("index"));
out.tx_pub_key = output_desc.second.get<string>("tx_pub_key");
//
using_outs.push_back(out);
}
vector<RandomAmountOutputs> mix_outs;
BOOST_FOREACH(boost::property_tree::ptree::value_type &mix_out_desc, json_root.get_child("mix_outs"))
{
@ -521,15 +567,20 @@ string serial_bridge::create_transaction(const string &args_string)
auto amountOutput = RandomAmountOutput{};
amountOutput.global_index = stoull(mix_out_output_desc.second.get<string>("global_index")); // this is, I believe, presently supplied as a string by the API, probably to avoid overflow
amountOutput.public_key = mix_out_output_desc.second.get<string>("public_key");
amountOutput.rct = mix_out_output_desc.second.get<string>("rct");
amountOutput.rct = mix_out_output_desc.second.get_optional<string>("rct");
amountAndOuts.outputs.push_back(amountOutput);
}
mix_outs.push_back(amountAndOuts);
}
//
Convenience_TransactionConstruction_RetVals retVals;
monero_transfer_utils::convenience__create_transaction(
optional<string> optl__passedIn_attemptAt_fee_string = json_root.get_optional<string>("passedIn_attemptAt_fee");
optional<uint64_t> optl__passedIn_attemptAt_fee = none;
if (optl__passedIn_attemptAt_fee_string != none) {
optl__passedIn_attemptAt_fee = stoull(*optl__passedIn_attemptAt_fee_string);
}
Send_Step2_RetVals retVals;
monero_transfer_utils::send_step2__reenterable_try_create_transaction(
retVals,
//
json_root.get<string>("from_address_string"),
json_root.get<string>("sec_viewKey_string"),
json_root.get<string>("sec_spendKey_string"),
@ -538,25 +589,31 @@ string serial_bridge::create_transaction(const string &args_string)
stoull(json_root.get<string>("sending_amount")),
stoull(json_root.get<string>("change_amount")),
stoull(json_root.get<string>("fee_amount")),
outputs,
stoul(json_root.get<string>("priority")),
using_outs,
stoull(json_root.get<string>("fee_per_b")),
mix_outs,
[] (uint8_t version, int64_t early_blocks) -> bool
{
return lightwallet_hardcoded__use_fork_rules(version, early_blocks);
},
stoull(json_root.get<string>("unlock_time")),
nettype
nettype_from_string(json_root.get<string>("nettype_string"))
);
boost::property_tree::ptree root;
if (retVals.errCode != noError) {
return error_ret_json_from_code(retVals.errCode, err_msg_from_err_code__create_transaction(retVals.errCode));
root.put(ret_json_key__any__err_code(), retVals.errCode);
} else {
if (retVals.tx_must_be_reconstructed) {
root.put(ret_json_key__send__tx_must_be_reconstructed(), true);
root.put(ret_json_key__send__fee_actually_needed(), std::move(RetVals_Transforms::str_from(retVals.fee_actually_needed))); // must be passed back
} else {
root.put(ret_json_key__send__tx_must_be_reconstructed(), false); // so consumers have it available
root.put(ret_json_key__send__serialized_signed_tx(), std::move(*(retVals.signed_serialized_tx_string)));
root.put(ret_json_key__send__tx_hash(), std::move(*(retVals.tx_hash_string)));
root.put(ret_json_key__send__tx_key(), std::move(*(retVals.tx_key_string)));
}
}
THROW_WALLET_EXCEPTION_IF(retVals.signed_serialized_tx_string == boost::none, error::wallet_internal_error, "Not expecting no signed_serialized_tx_string given no error");
//
boost::property_tree::ptree root;
root.put(ret_json_key__create_transaction__serialized_signed_tx(), std::move(*(retVals.signed_serialized_tx_string)));
root.put(ret_json_key__create_transaction__tx_hash(), std::move(*(retVals.tx_hash_string)));
root.put(ret_json_key__create_transaction__tx_key(), std::move(*(retVals.tx_key_string)));
//
return ret_json_from_root(root);
}
//

@ -42,7 +42,8 @@ namespace serial_bridge
using namespace cryptonote;
//
// Bridging Functions - these take and return JSON strings
string create_transaction(const string &args_string);
string send_step1__prepare_params_for_get_decoys(const string &args_string);
string send_step2__reenterable_try_create_transaction(const string &args_string);
//
string decode_address(const string &args_string);
string is_subaddress(const string &args_string);
@ -58,8 +59,6 @@ namespace serial_bridge
string seed_and_keys_from_mnemonic(const string &args_string);
string validate_components_for_login(const string &args_string);
//
string estimate_rct_tx_size(const string &args_string);
string calculate_fee(const string &args_string);
string estimated_tx_network_fee(const string &args_string);
//
string generate_key_image(const string &args_string);
@ -77,12 +76,24 @@ namespace serial_bridge
// - - Error
static inline string ret_json_key__any__err_msg() { return "err_msg"; } // optional
static inline string ret_json_key__any__err_code() { return "err_code"; } // optional
//
// - - Shared
static inline string ret_json_key__generic_retVal() { return "retVal"; }
// - - create_transaction
static inline string ret_json_key__create_transaction__serialized_signed_tx() { return "serialized_signed_tx"; }
static inline string ret_json_key__create_transaction__tx_hash() { return "tx_hash"; }
static inline string ret_json_key__create_transaction__tx_key() { return "tx_key"; }
// - - create_transaction / send
static inline string ret_json_key__send__spendable_balance() { return "spendable_balance"; }
static inline string ret_json_key__send__required_balance() { return "required_balance"; }
static inline string ret_json_key__send__mixin() { return "mixin"; }
static inline string ret_json_key__send__using_fee() { return "using_fee"; }
static inline string ret_json_key__send__final_total_wo_fee() { return "final_total_wo_fee"; }
static inline string ret_json_key__send__change_amount() { return "change_amount"; }
static inline string ret_json_key__send__using_outs() { return "using_outs"; } // this list's members' keys should probably be declared (is this the best way to do this?)
//
static inline string ret_json_key__send__tx_must_be_reconstructed() { return "tx_must_be_reconstructed"; }
static inline string ret_json_key__send__fee_actually_needed() { return "fee_actually_needed"; }
static inline string ret_json_key__send__serialized_signed_tx() { return "serialized_signed_tx"; }
static inline string ret_json_key__send__tx_hash() { return "tx_hash"; }
static inline string ret_json_key__send__tx_key() { return "tx_key"; }
//
// - - decode_address, etc
static inline string ret_json_key__paymentID_string() { return "paymentID_string"; } // optional
static inline string ret_json_key__isSubaddress() { return "isSubaddress"; }
@ -99,7 +110,7 @@ namespace serial_bridge
static inline string ret_json_key__decodeRct_mask() { return "mask"; }
static inline string ret_json_key__decodeRct_amount() { return "amount"; }
// JSON keys - Args
// TODO:
// TODO: (is there a better way of doing this?) structs with auto parse & serialization?
// static inline string args_json_key__
}

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save