mirror of
https://github.com/monero-project/monero.git
synced 2025-08-15 17:20:18 -04:00
wallet: feature: transfer amount with fee included
To transfer ~5 XMR to an address such that your balance drops by exactly 5 XMR, provide a `subtractfeefrom` flag to the `transfer` command. For example: transfer 76bDHojqFYiFCCYYtzTveJ8oFtmpNp3X1TgV2oKP7rHmZyFK1RvyE4r8vsJzf7SyNohMnbKT9wbcD3XUTgsZLX8LU5JBCfm 5 subtractfeefrom=all If my walet balance was exactly 30 XMR before this transaction, it will be exactly 25 XMR afterwards and the destination address will receive slightly less than 5 XMR. You can manually select which destinations fund the transaction fee and which ones do not by providing the destination index. For example: transfer 75sr8AAr... 3 74M7W4eg... 4 7AbWqDZ6... 5 subtractfeefrom=0,2 This will drop your balance by exactly 12 XMR including fees and will spread the fee cost proportionally (3:5 ratio) over destinations with addresses `75sr8AAr...` and `7AbWqDZ6...`, respectively. Disclaimer: This feature was paid for by @LocalMonero.
This commit is contained in:
parent
8eab181fe1
commit
51d7a6921c
10 changed files with 405 additions and 43 deletions
|
@ -196,7 +196,7 @@ namespace
|
|||
const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [uses] [index=<N1>[,<N2>[,...]]]");
|
||||
const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]");
|
||||
const char* USAGE_PAYMENT_ID("payment_id");
|
||||
const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]");
|
||||
const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [subtractfeefrom=<D0>[,<D1>,all,...]] [<payment_id>]");
|
||||
const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]");
|
||||
const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]");
|
||||
const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]");
|
||||
|
@ -531,7 +531,52 @@ namespace
|
|||
fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const char* SFFD_ARG_NAME{"subtractfeefrom="};
|
||||
|
||||
bool parse_subtract_fee_from_outputs
|
||||
(
|
||||
const std::string& arg,
|
||||
tools::wallet2::unique_index_container& subtract_fee_from_outputs,
|
||||
bool& subtract_fee_from_all,
|
||||
bool& matches
|
||||
)
|
||||
{
|
||||
matches = false;
|
||||
if (!boost::string_ref{arg}.starts_with(SFFD_ARG_NAME)) // if arg doesn't match
|
||||
return true;
|
||||
matches = true;
|
||||
|
||||
const char* arg_end = arg.c_str() + arg.size();
|
||||
for (const char* p = arg.c_str() + strlen(SFFD_ARG_NAME); p < arg_end;)
|
||||
{
|
||||
const char* new_p = nullptr;
|
||||
const unsigned long dest_index = strtoul(p, const_cast<char**>(&new_p), 10);
|
||||
if (dest_index == 0 && new_p == p) // numerical conversion failed
|
||||
{
|
||||
if (0 != strncmp(p, "all", 3))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to parse subtractfeefrom list");
|
||||
return false;
|
||||
}
|
||||
subtract_fee_from_all = true;
|
||||
break;
|
||||
}
|
||||
else if (dest_index > std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
fail_msg_writer() << tr("Destination index is too large") << ": " << dest_index;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
subtract_fee_from_outputs.insert(dest_index);
|
||||
p = new_p + 1; // skip the comma
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
|
||||
{
|
||||
|
@ -3290,7 +3335,7 @@ simple_wallet::simple_wallet()
|
|||
tr("Show the blockchain height."));
|
||||
m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::transfer, _1),
|
||||
tr(USAGE_TRANSFER),
|
||||
tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)"));
|
||||
tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included). The \"subtractfeefrom=\" list allows you to choose which destinations to fund the tx fee from instead of the change output. The fee will be split across the chosen destinations proportionally equally. For example, to make 3 transfers where the fee is taken from the first and third destinations, one could do: \"transfer <addr1> 3 <addr2> 0.5 <addr3> 1 subtractfeefrom=0,2\". Let's say the tx fee is 0.1. The balance would drop by exactly 4.5 XMR including fees, and addr1 & addr3 would receive 2.925 & 0.975 XMR, respectively. Use \"subtractfeefrom=all\" to spread the fee across all destinations."));
|
||||
m_cmd_binder.set_handler("locked_transfer",
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_transfer,_1),
|
||||
tr(USAGE_LOCKED_TRANSFER),
|
||||
|
@ -6640,6 +6685,27 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
|
|||
local_args.pop_back();
|
||||
}
|
||||
|
||||
// Parse subtractfeefrom destination list
|
||||
tools::wallet2::unique_index_container subtract_fee_from_outputs;
|
||||
bool subtract_fee_from_all = false;
|
||||
for (auto it = local_args.begin(); it < local_args.end();)
|
||||
{
|
||||
bool matches = false;
|
||||
if (!parse_subtract_fee_from_outputs(*it, subtract_fee_from_outputs, subtract_fee_from_all, matches))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (matches)
|
||||
{
|
||||
it = local_args.erase(it);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
vector<cryptonote::address_parse_info> dsts_info;
|
||||
vector<cryptonote::tx_destination_entry> dsts;
|
||||
for (size_t i = 0; i < local_args.size(); )
|
||||
|
@ -6736,6 +6802,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
|
|||
dsts.push_back(de);
|
||||
}
|
||||
|
||||
if (subtract_fee_from_all)
|
||||
{
|
||||
subtract_fee_from_outputs.clear();
|
||||
for (decltype(subtract_fee_from_outputs)::value_type i = 0; i < dsts.size(); ++i)
|
||||
subtract_fee_from_outputs.insert(i);
|
||||
}
|
||||
|
||||
SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;);
|
||||
|
||||
try
|
||||
|
@ -6754,13 +6827,13 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
|
|||
return false;
|
||||
}
|
||||
unlock_block = bc_height + locked_blocks;
|
||||
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
|
||||
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("Unknown transfer method, using default");
|
||||
/* FALLTHRU */
|
||||
case Transfer:
|
||||
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices);
|
||||
ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue