EXPERIMENTAL: Playing around with the idea of letting users attach a donation "per tx" (read comment in MoneroHandlerThread.java for why this is in quotes) to support MyNero development when they spend coins.

This commit is contained in:
pokkst 2022-12-03 21:52:17 -06:00
parent 4059e28d3f
commit fd84f11848
No known key found for this signature in database
GPG key ID: 90C2ED85E67A50FF
5 changed files with 131 additions and 2 deletions

View file

@ -233,6 +233,24 @@ std::vector<std::string> java2cpp(JNIEnv *env, jobject arrayList) {
return result; return result;
} }
jlong getElement(JNIEnv *env, jlongArray arr_j, int element) {
jlong result;
env->GetLongArrayRegion(arr_j, element,1, &result);
return result;
}
std::vector<std::uint64_t> java2cpp_long(JNIEnv *env, jlongArray longArray) {
jint len = env->GetArrayLength(longArray);
std::vector<std::uint64_t> result;
result.reserve(len);
for (jint i = 0; i < len; i++) {
jlong amount = getElement(env, longArray, i);
result.emplace_back(amount);
env->ReleaseLongArrayElements(longArray, reinterpret_cast<jlong *>(amount), i);
}
return result;
}
std::set<std::string> java2cpp_set(JNIEnv *env, jobject arrayList) { std::set<std::string> java2cpp_set(JNIEnv *env, jobject arrayList) {
jmethodID java_util_ArrayList_size = env->GetMethodID(class_ArrayList, "size", "()I"); jmethodID java_util_ArrayList_size = env->GetMethodID(class_ArrayList, "size", "()I");
@ -1029,6 +1047,29 @@ Java_net_mynero_wallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject inst
return reinterpret_cast<jlong>(tx); return reinterpret_cast<jlong>(tx);
} }
JNIEXPORT jlong JNICALL
Java_net_mynero_wallet_model_Wallet_createTransactionMultDestJ(JNIEnv *env, jobject instance,
jobject dst_addrs, jstring payment_id,
jlongArray amounts, jint mixin_count,
jint priority,
jint accountIndex, jobject key_images) {
const std::set<std::string> _key_images = java2cpp_set(env, key_images);
const std::vector<std::string> _dst_addrs = java2cpp(env, dst_addrs);
const std::vector<std::uint64_t> _dst_amounts = java2cpp_long(env, amounts);
const char *_payment_id = env->GetStringUTFChars(payment_id, nullptr);
Monero::PendingTransaction::Priority _priority =
static_cast<Monero::PendingTransaction::Priority>(priority);
Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance);
Monero::PendingTransaction *tx = wallet->createTransactionMultDest(_dst_addrs, _payment_id,
_dst_amounts, (uint32_t) mixin_count,
_priority,
(uint32_t) accountIndex, {}, _key_images);
env->ReleaseStringUTFChars(payment_id, _payment_id);
return reinterpret_cast<jlong>(tx);
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_net_mynero_wallet_model_Wallet_estimateTransactionFee(JNIEnv *env, jobject instance, Java_net_mynero_wallet_model_Wallet_estimateTransactionFee(JNIEnv *env, jobject instance,
jobject destinations, jint priority) { jobject destinations, jint priority) {

View file

@ -0,0 +1,22 @@
package net.mynero.wallet.model;
import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants;
public class TransactionOutput {
private final String destination;
private final long amount;
public TransactionOutput(String destination, long amount) {
this.destination = destination;
this.amount = amount;
}
public String getDestination() {
return destination;
}
public long getAmount() {
return amount;
}
}

View file

@ -288,6 +288,14 @@ public class Wallet {
txData.getPreferredInputs()); txData.getPreferredInputs());
} }
public PendingTransaction createSweepTransaction(String dst_addr, PendingTransaction.Priority priority, ArrayList<String> key_images) {
disposePendingTransaction();
int _priority = priority.getValue();
long txHandle = createSweepTransaction(dst_addr, "", 0, _priority, accountIndex, key_images);
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
public PendingTransaction createTransaction(String dst_addr, public PendingTransaction createTransaction(String dst_addr,
long amount, PendingTransaction.Priority priority, ArrayList<String> key_images) { long amount, PendingTransaction.Priority priority, ArrayList<String> key_images) {
disposePendingTransaction(); disposePendingTransaction();
@ -302,6 +310,26 @@ public class Wallet {
return pendingTransaction; return pendingTransaction;
} }
public PendingTransaction createTransactionMultDest(ArrayList<TransactionOutput> outputs, PendingTransaction.Priority priority, ArrayList<String> key_images) {
disposePendingTransaction();
int _priority = priority.getValue();
ArrayList<String> destinations = new ArrayList<>();
long[] amounts = new long[outputs.size()];
for(int i = 0; i < outputs.size(); i++) {
TransactionOutput output = outputs.get(i);
destinations.add(output.getDestination());
amounts[i] = output.getAmount();
}
long txHandle = createTransactionMultDestJ(destinations, "", amounts, 0, _priority,
accountIndex, key_images);
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
private native long createTransactionMultDestJ(ArrayList<String> dst_addrs, String payment_id,
long[] amount, int mixin_count,
int priority, int accountIndex, ArrayList<String> key_images);
private native long createTransactionJ(String dst_addr, String payment_id, private native long createTransactionJ(String dst_addr, String payment_id,
long amount, int mixin_count, long amount, int mixin_count,
int priority, int accountIndex, ArrayList<String> key_images); int priority, int accountIndex, ArrayList<String> key_images);

View file

@ -22,11 +22,13 @@ import net.mynero.wallet.data.Node;
import net.mynero.wallet.data.TxData; import net.mynero.wallet.data.TxData;
import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.CoinsInfo;
import net.mynero.wallet.model.PendingTransaction; import net.mynero.wallet.model.PendingTransaction;
import net.mynero.wallet.model.TransactionOutput;
import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.model.WalletListener; import net.mynero.wallet.model.WalletListener;
import net.mynero.wallet.model.WalletManager; import net.mynero.wallet.model.WalletManager;
import net.mynero.wallet.util.Constants; import net.mynero.wallet.util.Constants;
import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
@ -128,7 +130,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
} }
public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception { public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
long amount = sendAll ? Wallet.SWEEP_ALL : Wallet.getAmountFromString(amountStr); long amount = Wallet.getAmountFromString(amountStr);
ArrayList<String> preferredInputs; ArrayList<String> preferredInputs;
if (selectedUtxos.isEmpty()) { if (selectedUtxos.isEmpty()) {
// no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app // no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app
@ -137,7 +139,36 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
preferredInputs = selectedUtxos; preferredInputs = selectedUtxos;
checkSelectedAmounts(selectedUtxos, amount, sendAll); checkSelectedAmounts(selectedUtxos, amount, sendAll);
} }
return wallet.createTransaction(new TxData(address, amount, 0, feePriority, preferredInputs));
if(sendAll) {
return wallet.createSweepTransaction(address, feePriority, preferredInputs);
}
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false);
ArrayList<TransactionOutput> outputs = new ArrayList<>();
outputs.add(new TransactionOutput(address, amount));
if(donatePerTx) {
float randomDonatePct = getRandomDonateAmount(0.0075f, 0.015f); // occasionally attaches a 0.75% to 1.5% fee. It is random so that not even I know how much exactly you are sending.
/*
It's also not entirely "per tx". It won't always attach it so as to not have a consistent fingerprint on-chain. When it does attach a donation,
it will periodically split it up into 2 outputs instead of 1.
*/
int attachDonationRoll = new SecureRandom().nextInt(100);
if(attachDonationRoll > 75) {
int splitDonationRoll = new SecureRandom().nextInt(100);
long donateAmount = (long) (amount*randomDonatePct);
if(splitDonationRoll > 50) {
// split
long splitAmount = donateAmount / 2;
outputs.add(new TransactionOutput(Constants.DONATE_ADDRESS, splitAmount));
outputs.add(new TransactionOutput(Constants.DONATE_ADDRESS, splitAmount));
} else {
outputs.add(new TransactionOutput(Constants.DONATE_ADDRESS, donateAmount));
}
checkSelectedAmounts(selectedUtxos, amount+donateAmount, false); // check that the selected UTXOs satisfy the new amount total
}
}
return wallet.createTransactionMultDest(outputs, feePriority, preferredInputs);
} }
private void checkSelectedAmounts(ArrayList<String> selectedUtxos, long amount, boolean sendAll) throws Exception { private void checkSelectedAmounts(ArrayList<String> selectedUtxos, long amount, boolean sendAll) throws Exception {
@ -163,6 +194,12 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
return height % interval == 0; return height % interval == 0;
} }
private float getRandomDonateAmount(float min, float max) {
SecureRandom rand = new SecureRandom();
return rand.nextFloat() * (max - min) + min;
}
public interface Listener { public interface Listener {
void onRefresh(long height); void onRefresh(long height);

View file

@ -12,6 +12,7 @@ public class Constants {
public static final String PREF_USES_OFFSET = "pref_uses_offset"; public static final String PREF_USES_OFFSET = "pref_uses_offset";
public static final String PREF_STREET_MODE = "pref_street_mode"; public static final String PREF_STREET_MODE = "pref_street_mode";
public static final String PREF_MONEROCHAN = "pref_monerochan"; public static final String PREF_MONEROCHAN = "pref_monerochan";
public static final String PREF_DONATE_PER_TX = "pref_donate_per_tx";
public static final String URI_PREFIX = "monero:"; public static final String URI_PREFIX = "monero:";
public static final String URI_ARG_AMOUNT = "tx_amount"; public static final String URI_ARG_AMOUNT = "tx_amount";