diff --git a/monero_utils/MyMoneroCoreBridgeClass.js b/monero_utils/MyMoneroCoreBridgeClass.js index b09d1b4..0048f9f 100644 --- a/monero_utils/MyMoneroCoreBridgeClass.js +++ b/monero_utils/MyMoneroCoreBridgeClass.js @@ -37,6 +37,22 @@ const nettype_utils = require("../cryptonote_utils/nettype"); const MyMoneroCoreBridgeEssentialsClass = require('./MyMoneroCoreBridgeEssentialsClass') const MyMoneroBridge_utils = require('./MyMoneroBridge_utils') // +function bridge_sanitized__spendable_out(raw__out) +{ + const sanitary__output = + { + amount: raw__out.amount.toString(), + public_key: raw__out.public_key, + global_index: "" + raw__out.global_index, + index: "" + raw__out.index, + tx_pub_key: raw__out.tx_pub_key + }; + if (raw__out.rct && typeof raw__out.rct !== 'undefined') { + sanitary__output.rct = raw__out.rct; + } + return sanitary__output; +} +// class MyMoneroCoreBridgeClass extends MyMoneroCoreBridgeEssentialsClass { constructor(this_Module) @@ -437,6 +453,115 @@ class MyMoneroCoreBridgeClass extends MyMoneroCoreBridgeEssentialsClass } return ret.retVal; } + send_step2__try_create_transaction( // send only IPC-safe vals - no JSBigInts + from_address_string, + sec_keys, + to_address_string, + using_outs, + mix_outs, + fake_outputs_count, + final_total_wo_fee, + change_amount, + fee_amount, + payment_id, + priority, + fee_per_b, // not kib - if fee_per_kb, /= 1024 + fee_mask, + unlock_time, + nettype, + optl__fork_version + ) { + unlock_time = unlock_time || 0; + mix_outs = mix_outs || []; + // NOTE: we also do this check in the C++... may as well remove it from here + if (mix_outs.length !== using_outs.length && fake_outputs_count !== 0) { + return { + err_msg: "Wrong number of mix outs provided (" + + using_outs.length + " using_outs, " + + mix_outs.length + " mix outs)" + }; + } + for (var i = 0; i < mix_outs.length; i++) { + if ((mix_outs[i].outputs || []).length < fake_outputs_count) { + return { err_msg: "Not enough outputs to mix with" }; + } + } + // + // Now we need to convert all non-JSON-serializable objects such as JSBigInts to strings etc - not that there should be any! + // - and all numbers to strings - especially those which may be uint64_t on the receiving side + var sanitary__using_outs = []; + for (let i in using_outs) { + const sanitary__output = bridge_sanitized__spendable_out(using_outs[i]) + sanitary__using_outs.push(sanitary__output); + } + var sanitary__mix_outs = []; + for (let i in mix_outs) { + const sanitary__mix_outs_and_amount = + { + amount: mix_outs[i].amount.toString(), // it should be a string, but in case it's not + outputs: [] + }; + if (mix_outs[i].outputs && typeof mix_outs[i].outputs !== 'undefined') { + for (let j in mix_outs[i].outputs) { + const sanitary__mix_out = + { + global_index: "" + mix_outs[i].outputs[j].global_index, // number to string + public_key: mix_outs[i].outputs[j].public_key + }; + if (mix_outs[i].outputs[j].rct && typeof mix_outs[i].outputs[j].rct !== 'undefined') { + sanitary__mix_out.rct = mix_outs[i].outputs[j].rct; + } + sanitary__mix_outs_and_amount.outputs.push(sanitary__mix_out); + } + } + sanitary__mix_outs.push(sanitary__mix_outs_and_amount); + } + const args = + { + from_address_string: from_address_string, + sec_viewKey_string: sec_keys.view, + sec_spendKey_string: sec_keys.spend, + to_address_string: to_address_string, + final_total_wo_fee: final_total_wo_fee.toString(), + change_amount: change_amount.toString(), + fee_amount: fee_amount.toString(), + priority: "" + priority, + fee_per_b: fee_per_b.toString(), + fee_mask: fee_mask.toString(), + using_outs: sanitary__using_outs, + mix_outs: sanitary__mix_outs, + unlock_time: "" + unlock_time, // bridge is expecting a string + nettype_string: nettype_utils.nettype_to_API_string(nettype), + }; + if (typeof payment_id !== "undefined" && payment_id) { + args.payment_id_string = payment_id; + } + if (typeof optl__fork_version !== "undefined" && optl__fork_version && optl__fork_version != "") { + args.fork_version = optl__fork_version.toString(); + } + const args_str = JSON.stringify(args); + const ret_string = this.Module.send_step2__try_create_transaction(args_str); + const ret = JSON.parse(ret_string); + // + if (typeof ret.err_msg !== 'undefined' && ret.err_msg) { + return { err_msg: ret.err_msg, tx_must_be_reconstructed: false }; + } + if (ret.tx_must_be_reconstructed == "true" || ret.tx_must_be_reconstructed == true) { + if (typeof ret.fee_actually_needed == 'undefined' || !ret.fee_actually_needed) { + throw "tx_must_be_reconstructed; expected non-nil fee_actually_needed" + } + return { + tx_must_be_reconstructed: ret.tx_must_be_reconstructed, // if true, re-do procedure from step1 except for requesting UnspentOuts (that can be done oncet) + fee_actually_needed: ret.fee_actually_needed // can be passed back to step1 + } + } + return { // calling these out to set an interface + tx_must_be_reconstructed: false, // in case caller is not checking for nil + signed_serialized_tx: ret.serialized_signed_tx, // this name change should be fixed to serialized_signed_tx + tx_hash: ret.tx_hash, + tx_key: ret.tx_key + }; + } } // module.exports = MyMoneroCoreBridgeClass; \ No newline at end of file diff --git a/src/index.cpp b/src/index.cpp index 4b0f487..85500b0 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -75,6 +75,14 @@ string send_cb_III__submitted_tx(const string &args_string) return serial_bridge_utils::error_ret_json_from_message(e.what()); } } +string send_step2__try_create_transaction(const string &args_string) +{ + try { + return serial_bridge::send_step2__try_create_transaction(args_string); + } catch (std::exception &e) { + return serial_bridge_utils::error_ret_json_from_message(e.what()); + } +} // string decode_address(const string &args_string) { @@ -256,6 +264,7 @@ EMSCRIPTEN_BINDINGS(my_module) emscripten::function("send_cb_I__got_unspent_outs", &send_cb_I__got_unspent_outs); emscripten::function("send_cb_II__got_random_outs", &send_cb_II__got_random_outs); emscripten::function("send_cb_III__submitted_tx", &send_cb_III__submitted_tx); + emscripten::function("send_step2__try_create_transaction", &send_step2__try_create_transaction); // emscripten::function("decode_address", &decode_address); emscripten::function("is_subaddress", &is_subaddress); @@ -282,7 +291,6 @@ EMSCRIPTEN_BINDINGS(my_module) emscripten::function("decodeRctSimple", &decodeRctSimple); emscripten::function("derivation_to_scalar", &derivation_to_scalar); emscripten::function("encrypt_payment_id", &encrypt_payment_id); - // } extern "C" { // C -> JS diff --git a/tests/MyMoneroCoreCpp.node.js b/tests/MyMoneroCoreCpp.node.js index 6a4daa8..92879ad 100644 --- a/tests/MyMoneroCoreCpp.node.js +++ b/tests/MyMoneroCoreCpp.node.js @@ -31,6 +31,13 @@ function tests(Module) { console.log("Module is of type:", typeof Module) + { + console.time("send_step2__try_create_transaction") + const args_str = '{"final_total_wo_fee":"200000000","change_amount":"2733719296","fee_amount":"66280704","using_outs":[{"amount":"3000000000","public_key":"41be1978f58cabf69a9bed5b6cb3c8d588621ef9b67602328da42a213ee42271","rct":"86a2c9f1f8e66848cd99bfda7a14d4ac6c3525d06947e21e4e55fe42a368507eb5b234ccdd70beca8b1fc8de4f2ceb1374e0f1fd8810849e7f11316c2cc063060008ffa5ac9827b776993468df21af8c963d12148622354f950cbe1369a92a0c","global_index":"7611174","index":"1","tx_pub_key":"bd703d7f37995cc7071fb4d2929594b5e2a4c27d2b7c68a9064500ca7bc638b8"}],"payment_id_string":"d2f602b240fbe624","nettype_string":"MAINNET","to_address_string":"4APbcAKxZ2KPVPMnqa5cPtJK25tr7maE7LrJe67vzumiCtWwjDBvYnHZr18wFexJpih71Mxsjv8b7EpQftpB9NjPPXmZxHN","from_address_string":"43zxvpcj5Xv9SEkNXbMCG7LPQStHMpFCQCmkmR4u5nzjWwq5Xkv5VmGgYEsHXg4ja2FGRD5wMWbBVMijDTqmmVqm93wHGkg","sec_viewKey_string":"7bea1907940afdd480eff7c4bcadb478a0fbb626df9e3ed74ae801e18f53e104","sec_spendKey_string":"4e6d43cd03812b803c6f3206689f5fcc910005fc7e91d50d79b0776dbefcd803","fee_per_b":"24658","fee_mask":"10000","fork_version":"10","unlock_time":"0","priority":"1","mix_outs":[{"amount":"0","outputs":[{"global_index":"7453099","public_key":"31f3a7fec0f6f09067e826b6c2904fd4b1684d7893dcf08c5b5d22e317e148bb","rct":"ea6bcb193a25ce2787dd6abaaeef1ee0c924b323c6a5873db1406261e86145fc"},{"global_index":"7500097","public_key":"f9d923500671da05a1bf44b932b872f0c4a3c88e6b3d4bf774c8be915e25f42b","rct":"dcae4267a6c382bcd71fd1af4d2cbceb3749d576d7a3acc473dd579ea9231a52"},{"global_index":"7548483","public_key":"839cbbb73685654b93e824c4843e745e8d5f7742e83494932307bf300641c480","rct":"aa99d492f1d6f1b20dcd95b8fff8f67a219043d0d94b4551759016b4888573e7"},{"global_index":"7554755","public_key":"b8860f0697988c8cefd7b4285fbb8bec463f136c2b9a9cadb3e57cebee10717f","rct":"327f9b07bee9c4c25b5a990123cd2444228e5704ebe32016cd632866710279b5"},{"global_index":"7561477","public_key":"561d734cb90bc4a64d49d37f85ea85575243e2ed749a3d6dcb4d27aa6bec6e88","rct":"b5393e038df95b94bfda62b44a29141cac9e356127270af97193460d51949841"},{"global_index":"7567062","public_key":"db1024ef67e7e73608ef8afab62f49e2402c8da3dc3197008e3ba720ad3c94a8","rct":"1fedf95621881b77f823a70aa83ece26aef62974976d2b8cd87ed4862a4ec92c"},{"global_index":"7567508","public_key":"6283f3cd2f050bba90276443fe04f6076ad2ad46a515bf07b84d424a3ba43d27","rct":"10e16bb8a8b7b0c8a4b193467b010976b962809c9f3e6c047335dba09daa351f"},{"global_index":"7568716","public_key":"7a7deb4eef81c1f5ce9cbd0552891cb19f1014a03a5863d549630824c7c7c0d3","rct":"735d059dc3526334ac705ddc44c4316bb8805d2426dcea9544cde50cf6c7a850"},{"global_index":"7571196","public_key":"535208e354cae530ed7ce752935e555d630cf2edd7f91525024ed9c332b2a347","rct":"c3cf838faa14e993536c5581ca582fb0d96b70f713cf88f7f15c89336e5853ec"},{"global_index":"7571333","public_key":"e73f27b7eb001aa7eac13df82814cda65b42ceeb6ef36227c25d5cbf82f6a5e4","rct":"5f45f33c6800cdae202b37abe6d87b53d6873e7b30f3527161f44fa8db3104b6"},{"global_index":"7571335","public_key":"fce982db8e7a6b71a1e632c7de8c5cbf54e8bacdfbf250f1ffc2a8d2f7055ce3","rct":"407bdcc48e70eb3ef2cc22cefee6c6b5a3c59fd17bde12fda5f1a44a0fb39d14"}]}]}' + const ret_string = Module.send_step2__try_create_transaction(args_str) + console.log("send_step2__try_create_transaction ret", ret_string) + console.timeEnd("send_step2__try_create_transaction") + } { console.time("decode_address") const args =