const assert = require("assert");
const BigInteger = require("../../common/biginteger").BigInteger;
const GenUtils = require("../../common/GenUtils");
const MoneroDestination = require("./MoneroDestination");
const MoneroError = require("../../common/MoneroError");
/**
* Configures a transaction to send, sweep, or create a payment URI.
*/
class MoneroTxConfig {
/**
* <p>Generic request to transfer funds from a wallet.</p>
*
* <p>Examples:</p>
*
* <code>
* let config1 = new MoneroTxConfig({<br>
* accountIndex: 0,<br>
* address: "59aZULsUF3YN...",<br>
* amount: new BigInteger("500000"),<br>
* priority: MoneroTxPriority.NORMAL,<br>
* relay: true<br>
* });<br><br>
* </code>
*
* @param {MoneroTxConfig|object} config - configures the transaction to create (optional)
* @param {string} config.address - single destination address
* @param {BigInteger} config.amount - single destination amount
* @param {int} config.accountIndex - source account index to transfer funds from
* @param {int} config.subaddressIndex - source subaddress index to transfer funds from
* @param {int[]} config.subaddressIndices - source subaddress indices to transfer funds from
* @param {boolean} config.relay - relay the transaction to peers to commit to the blockchain
* @param {MoneroTxPriority} config.priority - transaction priority (default MoneroTxPriority.NORMAL)
* @param {MoneroDestination[]} config.destinations - addresses and amounts in a multi-destination tx
* @param {int[]} config.subtractFeeFrom - list of destination indices to split the transaction fee
* @param {string} config.paymentId - transaction payment ID
* @param {BigInteger} config.unlockTime - minimum height or timestamp for the transaction to unlock (default 0)
* @param {string} config.note - transaction note saved locally with the wallet
* @param {string} config.recipientName - recipient name saved locally with the wallet
* @param {boolean} config.canSplit - allow funds to be transferred using multiple transactions
* @param {BigInteger} config.belowAmount - for sweep requests, include outputs below this amount when sweeping wallet, account, subaddress, or all unlocked funds
* @param {boolean} config.sweepEachSubaddress - for sweep requests, sweep each subaddress individually instead of together if true
* @param {string} config.keyImage - key image to sweep (ignored except in sweepOutput() requests)
*/
constructor(config, relaxValidation) { // relax validation for internal use to process json from rpc or cpp
if (arguments.length > 2) throw new MoneroError("MoneroTxConfig can be constructed with only two parameters but was given " + arguments.length)
// initialize internal state
if (!config) this.state = {};
else if (config instanceof MoneroTxConfig) this.state = config.toJson();
else if (typeof config === "object") {
this.state = Object.assign({}, config);
if (relaxValidation) {
if (typeof this.state.amount === "number") this.state.amount = BigInteger.parse(this.state.amount);
if (typeof this.state.unlockTime === "number") this.state.unlockTime = BigInteger.parse(this.state.unlockTime);
if (typeof this.state.belowAmount === "number") this.state.belowAmount = BigInteger.parse(this.state.belowAmount);
}
// check for unsupported fields
for (let key of Object.keys(config)) {
if (!GenUtils.arrayContains(MoneroTxConfig.SUPPORTED_FIELDS, key)) {
throw new MoneroError("Unsupported field in MoneroTxConfig: '" + key + "'");
}
}
}
else throw new MoneroError("Invalid argument given to MoneroTxConfig: " + typeof config);
// deserialize BigIntegers
if (this.state.fee !== undefined && !(this.state.fee instanceof BigInteger)) this.state.fee = BigInteger.parse(this.state.fee);
if (this.state.unlockTime !== undefined && !(this.state.unlockTime instanceof BigInteger)) this.state.unlockTime = BigInteger.parse(this.state.unlockTime);
if (this.state.belowAmount !== undefined && !(this.state.belowAmount instanceof BigInteger)) this.state.belowAmount = BigInteger.parse(this.state.belowAmount);
// deserialize destinations
if (this.state.destinations) {
assert(this.state.address === undefined && this.state.amount === undefined, "Tx configuration may specify destinations or an address/amount but not both");
this.setDestinations(this.state.destinations.map(destination => destination instanceof MoneroDestination ? destination : new MoneroDestination(destination)));
}
// alias 'address' and 'amount' to single destination to support e.g. createTx({address: "..."})
if (this.state.address || this.state.amount) {
assert(!this.state.destinations, "Tx configuration may specify destinations or an address/amount but not both");
this.setAddress(this.state.address);
this.setAmount(this.state.amount);
delete this.state.address;
delete this.state.amount;
}
// alias 'subaddressIndex' to subaddress indices
if (this.state.subaddressIndex !== undefined) {
this.setSubaddressIndices([this.state.subaddressIndex]);
delete this.state.subaddressIndex;
}
}
copy() {
return new MoneroTxConfig(this);
}
toJson() {
let json = Object.assign({}, this.state); // copy state
if (this.getDestinations()) {
json.destinations = [];
for (let destination of this.getDestinations()) json.destinations.push(destination.toJson());
}
if (this.getFee()) json.fee = this.getFee().toString();
if (this.getUnlockTime()) json.unlockTime = this.getUnlockTime().toString();
if (this.getBelowAmount()) json.belowAmount = this.getBelowAmount().toString();
return json;
}
/**
* Set the address of a single-destination configuration.
*
* @param {string} address - the address to set for the single destination
* @return {MoneroTxConfig} this configuration for chaining
*/
setAddress(address) {
if (this.state.destinations !== undefined && this.state.destinations.length > 1) throw new MoneroError("Cannot set address because MoneroTxConfig already has multiple destinations");
if (this.state.destinations === undefined || this.state.destinations.length === 0) this.addDestination(new MoneroDestination(address));
else this.state.destinations[0].setAddress(address);
return this;
}
/**
* Get the address of a single-destination configuration.
*
* @return {string} the address of the single destination
*/
getAddress() {
if (this.state.destinations === undefined || this.state.destinations.length !== 1) throw new MoneroError("Cannot get address because MoneroTxConfig does not have exactly one destination");
return this.state.destinations[0].getAddress();
}
/**
* Set the amount of a single-destination configuration.
*
* @param {BigInteger|string} amount - the amount to set for the single destination
* @return {MoneroTxConfig} this configuration for chaining
*/
setAmount(amount) {
if (amount !== undefined && !(this.state.amount instanceof BigInteger)) {
if (typeof amount === "number") throw new MoneroError("Destination amount must be BigInteger or string");
try { amount = BigInteger.parse(amount); }
catch (err) { throw new MoneroError("Invalid destination amount: " + amount); }
}
if (this.state.destinations !== undefined && this.state.destinations.length > 1) throw new MoneroError("Cannot set amount because MoneroTxConfig already has multiple destinations");
if (this.state.destinations === undefined || this.state.destinations.length === 0) this.addDestination(new MoneroDestination(undefined, amount));
else this.state.destinations[0].setAmount(amount);
return this;
}
/**
* Get the amount of a single-destination configuration.
*
* @return {BigInteger} the amount of the single destination
*/
getAmount() {
if (this.state.destinations === undefined || this.state.destinations.length !== 1) throw new MoneroError("Cannot get amount because MoneroTxConfig does not have exactly one destination");
return this.state.destinations[0].getAmount();
}
addDestination(destinationOrAddress, amount) {
if (typeof destinationOrAddress === "string") return this.addDestination(new MoneroDestination(destinationOrAddress, amount));
assert(destinationOrAddress instanceof MoneroDestination);
if (this.state.destinations === undefined) this.state.destinations = [];
this.state.destinations.push(destinationOrAddress);
return this;
}
getDestinations() {
return this.state.destinations;
}
setDestinations(destinations) {
if (arguments.length > 1) destinations = Array.from(arguments);
this.state.destinations = destinations;
return this;
}
setDestination(destination) {
return this.setDestinations(destination ? [destination] : destination);
}
getSubtractFeeFrom() {
return this.state.subtractFeeFrom;
}
setSubtractFeeFrom(destinationIndices) {
if (arguments.length > 1) destinationIndices = Array.from(arguments);
this.state.subtractFeeFrom = destinationIndices;
return this;
}
getPaymentId() {
return this.state.paymentId;
}
setPaymentId(paymentId) {
this.state.paymentId = paymentId;
return this;
}
getPriority() {
return this.state.priority;
}
setPriority(priority) {
this.state.priority = priority;
return this;
}
getFee() {
return this.state.fee;
}
setFee(fee) {
this.state.fee = fee;
return this;
}
getAccountIndex() {
return this.state.accountIndex;
}
setAccountIndex(accountIndex) {
this.state.accountIndex = accountIndex;
return this;
}
setSubaddressIndex(subaddressIndex) {
this.setSubaddressIndices([subaddressIndex]);
return this;
}
getSubaddressIndices() {
return this.state.subaddressIndices;
}
setSubaddressIndices(subaddressIndices) {
if (arguments.length > 1) subaddressIndices = Array.from(arguments);
this.state.subaddressIndices = subaddressIndices;
return this;
}
getUnlockTime() {
return this.state.unlockTime;
}
setUnlockTime(unlockTime) {
if (unlockTime !== undefined) {
if (typeof unlockTime === "number") unlockTime = "" + unlockTime;
if (!(unlockTime instanceof BigInteger)) {
try { unlockTime = BigInteger.parse(unlockTime); }
catch (err) { throw new MoneroError("Invalid unlock time: " + unlockTime); }
}
}
this.state.unlockTime = unlockTime;
return this;
}
getRelay() {
return this.state.relay;
}
setRelay(relay) {
this.state.relay = relay;
return this;
}
getCanSplit() {
return this.state.canSplit;
}
setCanSplit(canSplit) {
this.state.canSplit = canSplit;
return this;
}
getNote() {
return this.state.note;
}
setNote(note) {
this.state.note = note;
return this;
}
getRecipientName() {
return this.state.recipientName;
}
setRecipientName(recipientName) {
this.state.recipientName = recipientName;
return this;
}
// --------------------------- SPECIFIC TO SWEEP ----------------------------
getBelowAmount() {
return this.state.belowAmount;
}
setBelowAmount(belowAmount) {
this.state.belowAmount = belowAmount;
return this;
}
getSweepEachSubaddress() {
return this.state.sweepEachSubaddress;
}
setSweepEachSubaddress(sweepEachSubaddress) {
this.state.sweepEachSubaddress = sweepEachSubaddress;
return this;
}
/**
* Get the key image hex of the output to sweep.
*
* return {string} is the key image hex of the output to sweep
*/
getKeyImage() {
return this.state.keyImage;
}
/**
* Set the key image hex of the output to sweep.
*
* @param {string} keyImage is the key image hex of the output to sweep
*/
setKeyImage(keyImage) {
this.state.keyImage = keyImage;
return this;
}
}
MoneroTxConfig.SUPPORTED_FIELDS = ["address", "amount", "accountIndex", "subaddressIndex", "subaddressIndices", "relay", "priority", "destinations", "subtractFeeFrom", "paymentId", "unlockTime", "note", "recipientName", "canSplit", "belowAmount", "sweepEachSubaddress", "keyImage"];
module.exports = MoneroTxConfig