@ -31,7 +31,7 @@
const async = require ( "async" ) ;
//
const monero _config = require ( "./monero_config" ) ;
const monero _utils = require ( "./monero_cryptonote_utils_instance" ) ;
const monero _utils _promise = require ( './monero_utils' )
const monero _amount _format _utils = require ( "./monero_amount_format_utils" ) ;
const monero _paymentID _utils = require ( "./monero_paymentID_utils" ) ;
const JSBigInt = require ( "../cryptonote_utils/biginteger" ) . BigInteger ;
@ -213,21 +213,14 @@ function SendFunds(
// err
// )
) {
const mixin = fixedMixin ( ) ;
var isRingCT = true ;
var sweeping = isSweep _orZeroWhenAmount === true ; // rather than, say, undefined
//
// some callback trampoline function declarations…
function _ _trampolineFor _success (
moneroReady _targetDescription _address ,
sentAmount ,
final _ _payment _id ,
tx _hash ,
tx _fee ,
tx _key ,
mixin ,
) {
success _fn (
monero _utils _promise . then ( function ( monero _utils )
{
const mixin = fixedMixin ( ) ;
var isRingCT = true ;
var sweeping = isSweep _orZeroWhenAmount === true ; // rather than, say, undefined
//
// some callback trampoline function declarations…
function _ _trampolineFor _success (
moneroReady _targetDescription _address ,
sentAmount ,
final _ _payment _id ,
@ -235,498 +228,508 @@ function SendFunds(
tx _fee ,
tx _key ,
mixin ,
) ;
}
function _ _trampolineFor _err _withErr ( err ) {
failWithErr _fn ( err ) ;
}
function _ _trampolineFor _err _withStr ( errStr ) {
const err = new Error ( errStr ) ;
console . error ( errStr ) ;
failWithErr _fn ( err ) ;
}
//
// parse & normalize the target descriptions by mapping them to Monero addresses & amounts
var amount = sweeping ? 0 : amount _orZeroWhenSweep ;
const targetDescription = {
address : target _address ,
amount : amount ,
} ;
new _moneroReadyTargetDescriptions _fromTargetDescriptions (
monero _openalias _utils ,
[ targetDescription ] , // requires a list of descriptions - but SendFunds was
// not written with multiple target support as MyMonero does not yet support it
nettype ,
function ( err , moneroReady _targetDescriptions ) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
const invalidOrZeroDestination _errStr =
"You need to enter a valid destination" ;
if ( moneroReady _targetDescriptions . length === 0 ) {
_ _trampolineFor _err _withStr ( invalidOrZeroDestination _errStr ) ;
return ;
}
const moneroReady _targetDescription =
moneroReady _targetDescriptions [ 0 ] ;
if (
moneroReady _targetDescription === null ||
typeof moneroReady _targetDescription === "undefined"
) {
_ _trampolineFor _err _withStr ( invalidOrZeroDestination _errStr ) ;
return ;
}
_proceedTo _prepareToSendFundsTo _moneroReady _targetDescription (
moneroReady _targetDescription ,
) {
success _fn (
moneroReady _targetDescription _address ,
sentAmount ,
final _ _payment _id ,
tx _hash ,
tx _fee ,
tx _key ,
mixin ,
) ;
} ,
) ;
function _proceedTo _prepareToSendFundsTo _moneroReady _targetDescription (
moneroReady _targetDescription ,
) {
var moneroReady _targetDescription _address =
moneroReady _targetDescription . address ;
var moneroReady _targetDescription _amount =
moneroReady _targetDescription . amount ;
//
var totalAmountWithoutFee _JSBigInt = new JSBigInt ( 0 ) . add (
moneroReady _targetDescription _amount ,
) ;
console . log (
"💬 Total to send, before fee: " + sweeping
? "all"
: monero _amount _format _utils . formatMoney ( totalAmountWithoutFee _JSBigInt ) ,
) ;
if ( ! sweeping && totalAmountWithoutFee _JSBigInt . compare ( 0 ) <= 0 ) {
const errStr = "The amount you've entered is too low" ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
function _ _trampolineFor _err _withErr ( err ) {
failWithErr _fn ( err ) ;
}
function _ _trampolineFor _err _withStr ( errStr ) {
const err = new Error ( errStr ) ;
console . error ( errStr ) ;
failWithErr _fn ( err ) ;
}
//
// Derive/finalize some values…
var final _ _payment _id = payment _id ;
var final _ _pid _encrypt = false ; // we don't want to encrypt payment ID unless we find an integrated one
var address _ _decode _result ;
try {
address _ _decode _result = monero _utils . decode _address (
moneroReady _targetDescription _address ,
nettype ,
// parse & normalize the target descriptions by mapping them to Monero addresses & amounts
var amount = sweeping ? 0 : amount _orZeroWhenSweep ;
const targetDescription = {
address : target _address ,
amount : amount ,
} ;
new _moneroReadyTargetDescriptions _fromTargetDescriptions (
monero _openalias _utils ,
[ targetDescription ] , // requires a list of descriptions - but SendFunds was
// not written with multiple target support as MyMonero does not yet support it
nettype ,
function ( err , moneroReady _targetDescriptions ) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
const invalidOrZeroDestination _errStr =
"You need to enter a valid destination" ;
if ( moneroReady _targetDescriptions . length === 0 ) {
_ _trampolineFor _err _withStr ( invalidOrZeroDestination _errStr ) ;
return ;
}
const moneroReady _targetDescription =
moneroReady _targetDescriptions [ 0 ] ;
if (
moneroReady _targetDescription === null ||
typeof moneroReady _targetDescription === "undefined"
) {
_ _trampolineFor _err _withStr ( invalidOrZeroDestination _errStr ) ;
return ;
}
_proceedTo _prepareToSendFundsTo _moneroReady _targetDescription (
moneroReady _targetDescription ,
) ;
} ,
) ;
function _proceedTo _prepareToSendFundsTo _moneroReady _targetDescription (
moneroReady _targetDescription ,
) {
var moneroReady _targetDescription _address =
moneroReady _targetDescription . address ;
var moneroReady _targetDescription _amount =
moneroReady _targetDescription . amount ;
//
var totalAmountWithoutFee _JSBigInt = new JSBigInt ( 0 ) . add (
moneroReady _targetDescription _amount ,
) ;
} catch ( e ) {
_ _trampolineFor _err _withStr (
typeof e === "string" ? e : e . toString ( ) ,
console . log (
"💬 Total to send, before fee: " + sweeping
? "all"
: monero _amount _format _utils . formatMoney ( totalAmountWithoutFee _JSBigInt ) ,
) ;
return ;
}
if ( payment _id ) {
if ( address _ _decode _result . intPaymentId ) {
const errStr =
"Payment ID must be blank when using an Integrated Address" ;
if ( ! sweeping && totalAmountWithoutFee _JSBigInt . compare ( 0 ) <= 0 ) {
const errStr = "The amount you've entered is too low" ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
} else if (
monero _utils . is _subaddress (
}
//
// Derive/finalize some values…
var final _ _payment _id = payment _id ;
var final _ _pid _encrypt = false ; // we don't want to encrypt payment ID unless we find an integrated one
var address _ _decode _result ;
try {
address _ _decode _result = monero _utils . decode _address (
moneroReady _targetDescription _address ,
nettype ,
)
) ;
} catch ( e ) {
_ _trampolineFor _err _withStr (
typeof e === "string" ? e : e . toString ( ) ,
) ;
return ;
}
if ( payment _id ) {
if ( address _ _decode _result . intPaymentId ) {
const errStr =
"Payment ID must be blank when using an Integrated Address" ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
} else if (
monero _utils . is _subaddress (
moneroReady _targetDescription _address ,
nettype ,
)
) {
const errStr =
"Payment ID must be blank when using a Subaddress" ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
}
if ( address _ _decode _result . intPaymentId ) {
final _ _payment _id = address _ _decode _result . intPaymentId ;
final _ _pid _encrypt = true ; // we do want to encrypt if using an integrated address
} else if (
monero _paymentID _utils . IsValidPaymentIDOrNoPaymentID (
final _ _payment _id ,
) === false
) {
const errStr =
"Payment ID must be blank when using a Subaddress" ;
const errStr = "Invalid payment ID." ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
}
if ( address _ _decode _result . intPaymentId ) {
final _ _payment _id = address _ _decode _result . intPaymentId ;
final _ _pid _encrypt = true ; // we do want to encrypt if using an integrated address
} else if (
monero _paymentID _utils . IsValidPaymentIDOrNoPaymentID (
//
_proceedTo _getUnspentOutsUsableForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
) === false
) {
const errStr = "Invalid payment ID." ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
final _ _pid _encrypt ,
) ;
}
//
_proceedTo _getUnspentOutsUsableForMixin (
function _proceedTo _getUnspentOutsUsableForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
) ;
}
function _proceedTo _getUnspentOutsUsableForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id , // non-existent or valid
final _ _pid _encrypt , // true or false
) {
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . fetchingLatestBalance ,
) ;
hostedMoneroAPIClient . UnspentOuts (
wallet _ _public _address ,
wallet _ _private _keys . view ,
wallet _ _public _keys . spend ,
wallet _ _private _keys . spend ,
mixin ,
sweeping ,
function ( err , unspentOuts , unusedOuts , dynamic _feePerKB _JSBigInt ) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
console . log (
"Received dynamic per kb fee" ,
monero _amount _format _utils . formatMoneySymbol ( dynamic _feePerKB _JSBigInt ) ,
) ;
_proceedTo _constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
dynamic _feePerKB _JSBigInt ,
) ;
} ,
) ;
}
function _proceedTo _constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
dynamic _feePerKB _JSBigInt ,
) {
// status: constructing transaction…
const feePerKB _JSBigInt = dynamic _feePerKB _JSBigInt ;
// Transaction will need at least 1KB fee (or 13KB for RingCT)
const network _minimumTXSize _kb = /*isRingCT ? */ 13 ; /* : 1*/
const network _minimumFee = calculate _fee _ _kb (
feePerKB _JSBigInt ,
network _minimumTXSize _kb ,
fee _multiplier _for _priority ( simple _priority ) ,
) ;
// ^-- now we're going to try using this minimum fee but the codepath has to be able to be re-entered if we find after constructing the whole tx that it is larger in kb than the minimum fee we're attempting to send it off with
_ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
final _ _payment _id , // non-existent or valid
final _ _pid _encrypt , // true or false
) {
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . fetchingLatestBalance ,
) ;
hostedMoneroAPIClient . UnspentOuts (
wallet _ _public _address ,
wallet _ _private _keys . view ,
wallet _ _public _keys . spend ,
wallet _ _private _keys . spend ,
mixin ,
sweeping ,
function ( err , unspentOuts , unusedOuts , dynamic _feePerKB _JSBigInt ) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
console . log (
"Received dynamic per kb fee" ,
monero _amount _format _utils . formatMoneySymbol ( dynamic _feePerKB _JSBigInt ) ,
) ;
_proceedTo _constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
dynamic _feePerKB _JSBigInt ,
) ;
} ,
) ;
}
function _proceedTo _constructFundTransferListAndSendFundsByUsingUnusedUnspentOutsForMixin (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
feePerKB _JSBigInt , // obtained from server, so passed in
network _minimumFee ,
) ;
}
function _ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
feePerKB _JSBigInt ,
passedIn _attemptAt _network _minimumFee ,
) {
// Now we need to establish some values for balance validation and to construct the transaction
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . calculatingFee ,
) ;
//
var attemptAt _network _minimumFee = passedIn _attemptAt _network _minimumFee ; // we may change this if isRingCT
// const hostingService_chargeAmount = hostedMoneroAPIClient.HostingServiceChargeFor_transactionWithNetworkFee(attemptAt_network_minimumFee)
var totalAmountIncludingFees ;
if ( sweeping ) {
totalAmountIncludingFees = new JSBigInt ( "18450000000000000000" ) ; //~uint64 max
console . log ( "Balance required: all" ) ;
} else {
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
attemptAt _network _minimumFee ,
) ; /*.add(hostingService_chargeAmount) NOTE service fee removed for now */
console . log (
"Balance required: " +
monero _amount _format _utils . formatMoneySymbol ( totalAmountIncludingFees ) ,
dynamic _feePerKB _JSBigInt ,
) {
// status: constructing transaction…
const feePerKB _JSBigInt = dynamic _feePerKB _JSBigInt ;
// Transaction will need at least 1KB fee (or 13KB for RingCT)
const network _minimumTXSize _kb = /*isRingCT ? */ 13 ; /* : 1*/
const network _minimumFee = calculate _fee _ _kb (
feePerKB _JSBigInt ,
network _minimumTXSize _kb ,
fee _multiplier _for _priority ( simple _priority ) ,
) ;
// ^-- now we're going to try using this minimum fee but the codepath has to be able to be re-entered if we find after constructing the whole tx that it is larger in kb than the minimum fee we're attempting to send it off with
_ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
feePerKB _JSBigInt , // obtained from server, so passed in
network _minimumFee ,
) ;
}
const usableOutputsAndAmounts = _outputsAndAmountToUseForMixin (
totalAmountIncludingFees ,
function _ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
isRingCT ,
sweeping ,
) ;
// v-- now if RingCT compute fee as closely as possible before hand
var usingOuts = usableOutputsAndAmounts . usingOuts ;
var usingOutsAmount = usableOutputsAndAmounts . usingOutsAmount ;
var remaining _unusedOuts = usableOutputsAndAmounts . remaining _unusedOuts ; // this is a copy of the pre-mutation usingOuts
if ( /*usingOuts.length > 1 &&*/ isRingCT ) {
var newNeededFee = calculate _fee (
feePerKB _JSBigInt ,
estimateRctSize ( usingOuts . length , mixin , 2 ) ,
fee _multiplier _for _priority ( simple _priority ) ,
feePerKB _JSBigInt ,
passedIn _attemptAt _network _minimumFee ,
) {
// Now we need to establish some values for balance validation and to construct the transaction
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . calculatingFee ,
) ;
// if newNeededFee < neededFee, use neededFee instead (should only happen on the 2nd or later times through (due to estimated fee being too low))
if ( newNeededFee . compare ( attemptAt _network _minimumFee ) < 0 ) {
newNeededFee = attemptAt _network _minimumFee ;
}
//
var attemptAt _network _minimumFee = passedIn _attemptAt _network _minimumFee ; // we may change this if isRingCT
// const hostingService_chargeAmount = hostedMoneroAPIClient.HostingServiceChargeFor_transactionWithNetworkFee(attemptAt_network_minimumFee)
var totalAmountIncludingFees ;
if ( sweeping ) {
/ *
// When/if sending to multiple destinations supported, uncomment and port this:
if ( dsts . length !== 1 ) {
deferred . reject ( "Sweeping to multiple accounts is not allowed" ) ;
return ;
}
* /
totalAmountWithoutFee _JSBigInt = usingOutsAmount . subtract (
newNeededFee ,
) ;
if ( totalAmountWithoutFee _JSBigInt . compare ( 0 ) < 1 ) {
const errStr = ` Your spendable balance is too low. Have ${ monero _amount _format _utils . formatMoney (
usingOutsAmount ,
) } $ {
monero _config . coinSymbol
} spendable , need $ { monero _amount _format _utils . formatMoney (
newNeededFee ,
) } $ { monero _config . coinSymbol } . ` ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
newNeededFee ,
) ;
totalAmountIncludingFees = new JSBigInt ( "18450000000000000000" ) ; //~uint64 max
console . log ( "Balance required: all" ) ;
} else {
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
newNeededFee ,
attemptAt _network _minimumFee ,
) ; /*.add(hostingService_chargeAmount) NOTE service fee removed for now */
console . log (
"Balance required: " +
monero _amount _format _utils . formatMoneySymbol ( totalAmountIncludingFees ) ,
) ;
// add outputs 1 at a time till we either have them all or can meet the fee
while (
usingOutsAmount . compare ( totalAmountIncludingFees ) < 0 &&
remaining _unusedOuts . length > 0
) {
const out = _popAndReturnRandomElementFromList (
remaining _unusedOuts ,
) ;
console . log (
"Using output: " +
monero _amount _format _utils . formatMoney ( out . amount ) +
" - " +
JSON . stringify ( out ) ,
}
const usableOutputsAndAmounts = _outputsAndAmountToUseForMixin (
totalAmountIncludingFees ,
unusedOuts ,
isRingCT ,
sweeping ,
) ;
// v-- now if RingCT compute fee as closely as possible before hand
var usingOuts = usableOutputsAndAmounts . usingOuts ;
var usingOutsAmount = usableOutputsAndAmounts . usingOutsAmount ;
var remaining _unusedOuts = usableOutputsAndAmounts . remaining _unusedOuts ; // this is a copy of the pre-mutation usingOuts
if ( /*usingOuts.length > 1 &&*/ isRingCT ) {
var newNeededFee = calculate _fee (
feePerKB _JSBigInt ,
estimateRctSize ( usingOuts . length , mixin , 2 ) ,
fee _multiplier _for _priority ( simple _priority ) ,
) ;
// if newNeededFee < neededFee, use neededFee instead (should only happen on the 2nd or later times through (due to estimated fee being too low))
if ( newNeededFee . compare ( attemptAt _network _minimumFee ) < 0 ) {
newNeededFee = attemptAt _network _minimumFee ;
}
if ( sweeping ) {
/ *
// When/if sending to multiple destinations supported, uncomment and port this:
if ( dsts . length !== 1 ) {
deferred . reject ( "Sweeping to multiple accounts is not allowed" ) ;
return ;
}
* /
totalAmountWithoutFee _JSBigInt = usingOutsAmount . subtract (
newNeededFee ,
) ;
// and recalculate invalidated values
newNeededFee = calculate _fee (
feePerKB _JSBigInt ,
estimateRctSize (
usingOuts . length ,
mixin ,
2 ,
) ,
fee _multiplier _for _priority ( simple _priority ) ,
if ( totalAmountWithoutFee _JSBigInt . compare ( 0 ) < 1 ) {
const errStr = ` Your spendable balance is too low. Have ${ monero _amount _format _utils . formatMoney (
usingOutsAmount ,
) } $ {
monero _config . coinSymbol
} spendable , need $ { monero _amount _format _utils . formatMoney (
newNeededFee ,
) } $ { monero _config . coinSymbol } . ` ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
newNeededFee ,
) ;
} else {
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
newNeededFee ,
) ;
// add outputs 1 at a time till we either have them all or can meet the fee
while (
usingOutsAmount . compare ( totalAmountIncludingFees ) < 0 &&
remaining _unusedOuts . length > 0
) {
const out = _popAndReturnRandomElementFromList (
remaining _unusedOuts ,
) ;
console . log (
"Using output: " +
monero _amount _format _utils . formatMoney ( out . amount ) +
" - " +
JSON . stringify ( out ) ,
) ;
// and recalculate invalidated values
newNeededFee = calculate _fee (
feePerKB _JSBigInt ,
estimateRctSize (
usingOuts . length ,
mixin ,
2 ,
) ,
fee _multiplier _for _priority ( simple _priority ) ,
) ;
totalAmountIncludingFees = totalAmountWithoutFee _JSBigInt . add (
newNeededFee ,
) ;
}
}
console . log (
"New fee: " +
monero _amount _format _utils . formatMoneySymbol ( newNeededFee ) +
" for " +
usingOuts . length +
" inputs" ,
) ;
attemptAt _network _minimumFee = newNeededFee ;
}
console . log (
"New fee: " +
monero _amount _format _utils . formatMoneySymbol ( newNeededFee ) +
" for " +
usingOuts . length +
" inputs" ,
"~ Balance required: " +
monero _amount _format _utils . formatMoneySymbol ( totalAmountIncludingFees ) ,
) ;
attemptAt _network _minimumFee = newNeededFee ;
}
console . log (
"~ Balance required: " +
monero _amount _format _utils . formatMoneySymbol ( totalAmountIncludingFees ) ,
) ;
// Now we can validate available balance with usingOutsAmount (TODO? maybe this check can be done before selecting outputs?)
const usingOutsAmount _comparedTo _totalAmount = usingOutsAmount . compare (
totalAmountIncludingFees ,
) ;
if ( usingOutsAmount _comparedTo _totalAmount < 0 ) {
const errStr = ` Your spendable balance is too low. Have ${ monero _amount _format _utils . formatMoney (
usingOutsAmount ,
) } $ {
monero _config . coinSymbol
} spendable , need $ { monero _amount _format _utils . formatMoney (
totalAmountIncludingFees ,
) } $ { monero _config . coinSymbol } . ` ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
//
// Must calculate change..
var changeAmount = JSBigInt ( "0" ) ; // to initialize
if ( usingOutsAmount _comparedTo _totalAmount > 0 ) {
if ( sweeping ) {
throw "Unexpected usingOutsAmount_comparedTo_totalAmount > 0 && sweeping" ;
}
changeAmount = usingOutsAmount . subtract (
// Now we can validate available balance with usingOutsAmount (TODO? maybe this check can be done before selecting outputs?)
const usingOutsAmount _comparedTo _totalAmount = usingOutsAmount . compare (
totalAmountIncludingFees ,
) ;
}
console . log ( "Calculated changeAmount:" , changeAmount ) ;
//
// first, grab RandomOuts, then enter __createTx
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . fetchingDecoyOutputs ,
) ;
hostedMoneroAPIClient . RandomOuts ( usingOuts , mixin , function (
err ,
amount _outs ,
) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
_ _createTxAndAttemptToSend ( amount _outs ) ;
} ) ;
//
function _ _createTxAndAttemptToSend ( mix _outs ) {
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . constructingTransaction ,
) ;
function printDsts ( dsts )
{
for ( var i = 0 ; i < dsts . length ; i ++ ) {
console . log ( dsts [ i ] . address + ": " + monero _amount _format _utils . formatMoneyFull ( dsts [ i ] . amount ) )
}
}
var create _transaction _ _retVals ;
try {
create _transaction _ _retVals = monero _utils . create _signed _transaction (
wallet _ _public _address ,
wallet _ _private _keys ,
target _address ,
usingOuts ,
mix _outs ,
mixin ,
totalAmountWithoutFee _JSBigInt . toString ( ) , // even though it's in dsts, sending it directly as core C++ takes it
changeAmount . toString ( ) ,
attemptAt _network _minimumFee . toString ( ) , // must serialize for IPC
final _ _payment _id ,
0 ,
isRingCT ,
nettype ,
) ;
console . log ( "got back" , create _transaction _ _retVals )
} catch ( e ) {
var errStr ;
if ( e ) {
errStr = typeof e == "string" ? e : e . toString ( ) ;
} else {
errStr = "Failed to create transaction with unknown error." ;
}
if ( usingOutsAmount _comparedTo _totalAmount < 0 ) {
const errStr = ` Your spendable balance is too low. Have ${ monero _amount _format _utils . formatMoney (
usingOutsAmount ,
) } $ {
monero _config . coinSymbol
} spendable , need $ { monero _amount _format _utils . formatMoney (
totalAmountIncludingFees ,
) } $ { monero _config . coinSymbol } . ` ;
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
console . log ( "created tx: " , JSON . stringify ( create _transaction _ _retVals ) ) ;
//
if ( typeof create _transaction _ _retVals . err _msg !== 'undefined' && create _transaction _ _retVals . err _msg ) {
// actually not expecting this! but just in case..
_ _trampolineFor _err _withStr ( create _transaction _ _retVals . err _msg ) ;
return ;
// Must calculate change..
var changeAmount = JSBigInt ( "0" ) ; // to initialize
if ( usingOutsAmount _comparedTo _totalAmount > 0 ) {
if ( sweeping ) {
throw "Unexpected usingOutsAmount_comparedTo_totalAmount > 0 && sweeping" ;
}
changeAmount = usingOutsAmount . subtract (
totalAmountIncludingFees ,
) ;
}
var serialized _signedTx = create _transaction _ _retVals . signed _serialized _tx ;
var tx _hash = create _transaction _ _retVals . tx _hash ;
var tx _key = create _transaction _ _retVals . tx _key ;
console . log ( "tx serialized: " + serialized _signedTx ) ;
console . log ( "Tx hash: " + tx _hash ) ;
console . log ( "Tx key: " + tx _key ) ;
console . log ( "Calculated changeAmount:" , changeAmount ) ;
//
// work out per-kb fee for transaction and verify that it's enough
var txBlobBytes = serialized _signedTx . length / 2 ;
var numKB = Math . floor ( txBlobBytes / 1024 ) ;
if ( txBlobBytes % 1024 ) {
numKB ++ ;
}
console . log (
txBlobBytes +
" bytes <= " +
numKB +
" KB (current fee: " +
monero _amount _format _utils . formatMoneyFull ( attemptAt _network _minimumFee ) +
")" ,
) ;
const feeActuallyNeededByNetwork = calculate _fee _ _kb (
feePerKB _JSBigInt ,
numKB ,
fee _multiplier _for _priority ( simple _priority ) ,
// first, grab RandomOuts, then enter __createTx
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . fetchingDecoyOutputs ,
) ;
// if we need a higher fee
if (
feeActuallyNeededByNetwork . compare (
attemptAt _network _minimumFee ,
) > 0
hostedMoneroAPIClient . RandomOuts ( usingOuts , mixin , function (
err ,
amount _outs ,
) {
if ( err ) {
_ _trampolineFor _err _withErr ( err ) ;
return ;
}
_ _createTxAndAttemptToSend ( amount _outs ) ;
} ) ;
//
function _ _createTxAndAttemptToSend ( mix _outs ) {
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . constructingTransaction ,
) ;
function printDsts ( dsts )
{
for ( var i = 0 ; i < dsts . length ; i ++ ) {
console . log ( dsts [ i ] . address + ": " + monero _amount _format _utils . formatMoneyFull ( dsts [ i ] . amount ) )
}
}
var create _transaction _ _retVals ;
try {
create _transaction _ _retVals = monero _utils . create _signed _transaction (
wallet _ _public _address ,
wallet _ _private _keys ,
target _address ,
usingOuts ,
mix _outs ,
mixin ,
totalAmountWithoutFee _JSBigInt . toString ( ) , // even though it's in dsts, sending it directly as core C++ takes it
changeAmount . toString ( ) ,
attemptAt _network _minimumFee . toString ( ) , // must serialize for IPC
final _ _payment _id ,
0 ,
isRingCT ,
nettype ,
) ;
console . log ( "got back" , create _transaction _ _retVals )
} catch ( e ) {
var errStr ;
if ( e ) {
errStr = typeof e == "string" ? e : e . toString ( ) ;
} else {
errStr = "Failed to create transaction with unknown error." ;
}
_ _trampolineFor _err _withStr ( errStr ) ;
return ;
}
console . log ( "created tx: " , JSON . stringify ( create _transaction _ _retVals ) ) ;
//
if ( typeof create _transaction _ _retVals . err _msg !== 'undefined' && create _transaction _ _retVals . err _msg ) {
// actually not expecting this! but just in case..
_ _trampolineFor _err _withStr ( create _transaction _ _retVals . err _msg ) ;
return ;
}
var serialized _signedTx = create _transaction _ _retVals . signed _serialized _tx ;
var tx _hash = create _transaction _ _retVals . tx _hash ;
var tx _key = create _transaction _ _retVals . tx _key ;
console . log ( "tx serialized: " + serialized _signedTx ) ;
console . log ( "Tx hash: " + tx _hash ) ;
console . log ( "Tx key: " + tx _key ) ;
//
// work out per-kb fee for transaction and verify that it's enough
var txBlobBytes = serialized _signedTx . length / 2 ;
var numKB = Math . floor ( txBlobBytes / 1024 ) ;
if ( txBlobBytes % 1024 ) {
numKB ++ ;
}
console . log (
"💬 Need to reconstruct the tx with enough of a network fee. Previous fee: " +
monero _amount _format _utils . formatMoneyFull (
attemptAt _network _minimumFee ,
) +
" New fee: " +
monero _amount _format _utils . formatMoneyFull (
feeActuallyNeededByNetwork ,
) ,
txBlobBytes +
" bytes <= " +
numKB +
" KB (current fee: " +
monero _amount _format _utils . formatMoneyFull ( attemptAt _network _minimumFee ) +
")" ,
) ;
// this will update status back to .calculatingFee
_ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
moneroReady _targetDescription _address ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
final _ _pid _encrypt ,
unusedOuts ,
const feeActuallyNeededByNetwork = calculate _fee _ _kb (
feePerKB _JSBigInt ,
feeActuallyNeededByNetwork , // we are re-entering this codepath after changing this feeActuallyNeededByNetwork
numKB ,
fee _multiplier _for _priority ( simple _priority ) ,
) ;
//
return ;
}
//
// generated with correct per-kb fee
const final _networkFee = attemptAt _network _minimumFee ; // just to make things clear
console . log (
"💬 Successful tx generation, submitting tx. Going with final_networkFee of " ,
monero _amount _format _utils . formatMoney ( final _networkFee ) ,
) ;
// status: submitting…
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . submittingTransaction ,
) ;
hostedMoneroAPIClient . SubmitSerializedSignedTransaction (
wallet _ _public _address ,
wallet _ _private _keys . view ,
serialized _signedTx ,
function ( err ) {
if ( err ) {
_ _trampolineFor _err _withStr (
"Something unexpected occurred when submitting your transaction: " +
err ,
) ;
return ;
}
const tx _fee = final _networkFee ; /*.add(hostingService_chargeAmount) NOTE: Service charge removed to reduce bloat for now */
_ _trampolineFor _success (
// if we need a higher fee
if (
feeActuallyNeededByNetwork . compare (
attemptAt _network _minimumFee ,
) > 0
) {
console . log (
"💬 Need to reconstruct the tx with enough of a network fee. Previous fee: " +
monero _amount _format _utils . formatMoneyFull (
attemptAt _network _minimumFee ,
) +
" New fee: " +
monero _amount _format _utils . formatMoneyFull (
feeActuallyNeededByNetwork ,
) ,
) ;
// this will update status back to .calculatingFee
_ _reenterable _constructFundTransferListAndSendFunds _findingLowestNetworkFee (
moneroReady _targetDescription _address ,
sweeping
? parseFloat (
monero _amount _format _utils . formatMoneyFull (
totalAmountWithoutFee _JSBigInt ,
) ,
)
: amount ,
totalAmountWithoutFee _JSBigInt ,
final _ _payment _id ,
tx _hash ,
tx _fee ,
tx _key ,
mixin ,
) ; // 🎉
} ,
) ;
final _ _pid _encrypt ,
unusedOuts ,
feePerKB _JSBigInt ,
feeActuallyNeededByNetwork , // we are re-entering this codepath after changing this feeActuallyNeededByNetwork
) ;
//
return ;
}
//
// generated with correct per-kb fee
const final _networkFee = attemptAt _network _minimumFee ; // just to make things clear
console . log (
"💬 Successful tx generation, submitting tx. Going with final_networkFee of " ,
monero _amount _format _utils . formatMoney ( final _networkFee ) ,
) ;
// status: submitting…
preSuccess _nonTerminal _statusUpdate _fn (
SendFunds _ProcessStep _Code . submittingTransaction ,
) ;
hostedMoneroAPIClient . SubmitSerializedSignedTransaction (
wallet _ _public _address ,
wallet _ _private _keys . view ,
serialized _signedTx ,
function ( err ) {
if ( err ) {
_ _trampolineFor _err _withStr (
"Something unexpected occurred when submitting your transaction: " +
err ,
) ;
return ;
}
const tx _fee = final _networkFee ; /*.add(hostingService_chargeAmount) NOTE: Service charge removed to reduce bloat for now */
_ _trampolineFor _success (
moneroReady _targetDescription _address ,
sweeping
? parseFloat (
monero _amount _format _utils . formatMoneyFull (
totalAmountWithoutFee _JSBigInt ,
) ,
)
: amount ,
final _ _payment _id ,
tx _hash ,
tx _fee ,
tx _key ,
mixin ,
) ; // 🎉
} ,
) ;
}
}
}
} ) ;
}
exports . SendFunds = SendFunds ;
//