From fd84f11848aa4633af81a47497cc78664292a789 Mon Sep 17 00:00:00 2001 From: pokkst Date: Sat, 3 Dec 2022 21:52:17 -0600 Subject: [PATCH] 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. --- app/src/main/cpp/monerujo.cpp | 41 +++++++++++++++++++ .../wallet/model/TransactionOutput.java | 22 ++++++++++ .../java/net/mynero/wallet/model/Wallet.java | 28 +++++++++++++ .../wallet/service/MoneroHandlerThread.java | 41 ++++++++++++++++++- .../net/mynero/wallet/util/Constants.java | 1 + 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/net/mynero/wallet/model/TransactionOutput.java diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index c6a109a..72d908b 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -233,6 +233,24 @@ std::vector java2cpp(JNIEnv *env, jobject arrayList) { return result; } +jlong getElement(JNIEnv *env, jlongArray arr_j, int element) { + jlong result; + env->GetLongArrayRegion(arr_j, element,1, &result); + return result; +} + +std::vector java2cpp_long(JNIEnv *env, jlongArray longArray) { + jint len = env->GetArrayLength(longArray); + std::vector 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(amount), i); + } + return result; +} + std::set java2cpp_set(JNIEnv *env, jobject arrayList) { 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(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 _key_images = java2cpp_set(env, key_images); + const std::vector _dst_addrs = java2cpp(env, dst_addrs); + const std::vector _dst_amounts = java2cpp_long(env, amounts); + const char *_payment_id = env->GetStringUTFChars(payment_id, nullptr); + Monero::PendingTransaction::Priority _priority = + static_cast(priority); + Monero::Wallet *wallet = getHandle(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(tx); +} + JNIEXPORT jlong JNICALL Java_net_mynero_wallet_model_Wallet_estimateTransactionFee(JNIEnv *env, jobject instance, jobject destinations, jint priority) { diff --git a/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java b/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java new file mode 100644 index 0000000..0480dc7 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/Wallet.java b/app/src/main/java/net/mynero/wallet/model/Wallet.java index cb02d30..a3736c1 100644 --- a/app/src/main/java/net/mynero/wallet/model/Wallet.java +++ b/app/src/main/java/net/mynero/wallet/model/Wallet.java @@ -288,6 +288,14 @@ public class Wallet { txData.getPreferredInputs()); } + public PendingTransaction createSweepTransaction(String dst_addr, PendingTransaction.Priority priority, ArrayList 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, long amount, PendingTransaction.Priority priority, ArrayList key_images) { disposePendingTransaction(); @@ -302,6 +310,26 @@ public class Wallet { return pendingTransaction; } + public PendingTransaction createTransactionMultDest(ArrayList outputs, PendingTransaction.Priority priority, ArrayList key_images) { + disposePendingTransaction(); + int _priority = priority.getValue(); + ArrayList 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 dst_addrs, String payment_id, + long[] amount, int mixin_count, + int priority, int accountIndex, ArrayList key_images); + private native long createTransactionJ(String dst_addr, String payment_id, long amount, int mixin_count, int priority, int accountIndex, ArrayList key_images); diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java index 36cc8f4..4b59c77 100644 --- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java @@ -22,11 +22,13 @@ import net.mynero.wallet.data.Node; import net.mynero.wallet.data.TxData; import net.mynero.wallet.model.CoinsInfo; import net.mynero.wallet.model.PendingTransaction; +import net.mynero.wallet.model.TransactionOutput; import net.mynero.wallet.model.Wallet; import net.mynero.wallet.model.WalletListener; import net.mynero.wallet.model.WalletManager; import net.mynero.wallet.util.Constants; +import java.security.SecureRandom; 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 selectedUtxos) throws Exception { - long amount = sendAll ? Wallet.SWEEP_ALL : Wallet.getAmountFromString(amountStr); + long amount = Wallet.getAmountFromString(amountStr); ArrayList preferredInputs; if (selectedUtxos.isEmpty()) { // 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; 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 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 selectedUtxos, long amount, boolean sendAll) throws Exception { @@ -163,6 +194,12 @@ public class MoneroHandlerThread extends Thread implements WalletListener { return height % interval == 0; } + private float getRandomDonateAmount(float min, float max) { + SecureRandom rand = new SecureRandom(); + return rand.nextFloat() * (max - min) + min; + + } + public interface Listener { void onRefresh(long height); diff --git a/app/src/main/java/net/mynero/wallet/util/Constants.java b/app/src/main/java/net/mynero/wallet/util/Constants.java index 939f956..9565cf8 100644 --- a/app/src/main/java/net/mynero/wallet/util/Constants.java +++ b/app/src/main/java/net/mynero/wallet/util/Constants.java @@ -12,6 +12,7 @@ public class Constants { 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_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_ARG_AMOUNT = "tx_amount";