feat: remove automatic donations, add donate to mysu label

This commit is contained in:
- 2024-09-15 13:07:29 +02:00
parent 4ee0e3134b
commit 8a769d0e23
10 changed files with 98 additions and 152 deletions

View file

@ -2,6 +2,8 @@ package net.mynero.wallet.fragment.dialog
import android.app.Activity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -78,6 +80,7 @@ class SendBottomSheetDialog : BottomSheetDialogFragment() {
private var scanAddressImageButton: ImageButton? = null
private var feeRadioGroup: RadioGroup? = null
private var donateTextView: TextView? = null
private var donatingTextView: TextView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -103,11 +106,28 @@ class SendBottomSheetDialog : BottomSheetDialogFragment() {
feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview)
selectedUtxosValueTextView = view.findViewById(R.id.selected_utxos_value_textview)
donateTextView = view.findViewById(R.id.donate_label_textview)
donatingTextView = view.findViewById(R.id.donating_label_textview)
donateTextView?.setOnClickListener {
addressEditText?.setText(
Constants.DONATE_ADDRESS
)
}
donatingTextView?.setOnClickListener {
addressEditText?.setText("")
}
addressEditText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
if (s.toString() == Constants.DONATE_ADDRESS) {
donatingTextView?.visibility = View.VISIBLE
donateTextView?.visibility = View.INVISIBLE
} else {
donatingTextView?.visibility = View.INVISIBLE
donateTextView?.visibility = View.VISIBLE
}
}
})
if (uriData != null) {
addressEditText?.setText(uriData?.address)
if (uriData?.hasAmount() == true) {

View file

@ -31,6 +31,7 @@ import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.TxService
import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.Helper
import net.mynero.wallet.util.UriData
@ -270,19 +271,22 @@ class SendFragment : Fragment() {
val removeOutputImageButton =
entryView.findViewById<ImageButton>(R.id.remove_output_imagebutton)
val addressField = entryView.findViewById<EditText>(R.id.address_edittext)
val donateTextView = entryView.findViewById<TextView>(R.id.donate_label)
val donatingTextView = entryView.findViewById<TextView>(R.id.donating_label)
donateTextView.setOnClickListener {
addressField.setText(
Constants.DONATE_ADDRESS
)
}
donatingTextView.setOnClickListener {
addressField.setText("")
}
addressField.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
charSequence: CharSequence,
i: Int,
i1: Int,
i2: Int
) {
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
val currentOutputs: Int = destCount
val uriData = UriData.parse(editable.toString())
val uriData = UriData.parse(s.toString())
if (uriData != null) {
// we have valid address
val hasPaymentId = uriData.hasPaymentId()
@ -302,6 +306,14 @@ class SendFragment : Fragment() {
// when send-all is false and this is our only dest and address is invalid, then show add output button
mViewModel?.setShowAddOutputButton(true)
}
if (s.toString() == Constants.DONATE_ADDRESS) {
donateTextView.visibility = View.INVISIBLE
donatingTextView.visibility = View.VISIBLE
} else {
donateTextView.visibility = View.VISIBLE
donatingTextView.visibility = View.INVISIBLE
}
}
})
entryView.findViewById<View>(R.id.paste_amount_imagebutton)

View file

@ -47,7 +47,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
private var selectNodeButton: Button? = null
private var streetModeSwitch: SwitchCompat? = null
private var monerochanSwitch: SwitchCompat? = null
private var donationSwitch: SwitchCompat? = null
private var useBundledTor: CheckBox? = null
private var displaySeedButton: Button? = null
private var displayUtxosButton: Button? = null
@ -68,7 +67,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
selectNodeButton = view.findViewById(R.id.select_node_button)
streetModeSwitch = view.findViewById(R.id.street_mode_switch)
monerochanSwitch = view.findViewById(R.id.monerochan_switch)
donationSwitch = view.findViewById(R.id.donate_per_tx_switch)
torSwitch = view.findViewById(R.id.tor_switch)
val proxySettingsLayout = view.findViewById<ConstraintLayout>(R.id.wallet_proxy_settings_layout)
walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
@ -88,8 +86,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
monerochanSwitch?.isChecked =
PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, Constants.DEFAULT_PREF_MONEROCHAN) == true
donationSwitch?.isChecked =
PrefService.instance?.getBoolean(Constants.PREF_DONATE_PER_TX, false) == true
useBundledTor?.isChecked = cachedUsingBundledTor
torSwitch?.isChecked = cachedUsingProxy
updateProxy(cachedProxyAddress, cachedProxyPort)
@ -110,10 +106,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
}
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
donationSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply()
}
streetModeSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_STREET_MODE, b)?.apply()
BalanceService.instance?.refreshBalance()

View file

@ -21,12 +21,9 @@ import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.TransactionOutput
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
import net.mynero.wallet.model.Wallet.Companion.getPaymentIdFromAddress
import net.mynero.wallet.model.Wallet.ConnectionStatus
import net.mynero.wallet.model.WalletListener
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.util.Constants
import java.security.SecureRandom
/**
* Handy class for starting a new thread that has a looper. The looper can then be
@ -158,65 +155,7 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
val address = dest.component1()
return wallet.createSweepTransaction(address, feePriority, preferredInputs)
}
val finalOutputs = maybeAddDonationOutputs(totalAmount, outputs, preferredInputs)
return wallet.createTransactionMultDest(finalOutputs, feePriority, preferredInputs)
}
@Throws(Exception::class)
private fun maybeAddDonationOutputs(
amount: Long,
outputs: List<TransactionOutput>,
preferredInputs: List<String>
): List<TransactionOutput> {
val newOutputs = ArrayList(outputs)
val networkType = WalletManager.instance?.networkType ?: return newOutputs
val mainDestination =
outputs[0] // at this point, for now, we should only have one item in the list. TODO: add multi-dest/pay-to-many feature in the UI
val paymentId =
getPaymentIdFromAddress(mainDestination.destination, networkType.value)
val donatePerTx = PrefService.instance?.getBoolean(Constants.PREF_DONATE_PER_TX, false)
if (donatePerTx == true && paymentId?.isEmpty() == true) { // only attach donation when no payment id is needed (i.e. integrated address)
val rand = SecureRandom()
val randomDonatePct = getRandomDonateAmount(
0.005f,
0.03f
) // occasionally attaches a 0.5% to 3% donation. 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 consistently uncommon fingerprint on-chain. When it does attach a donation,
it will periodically split it up into multiple outputs instead of one.
*/
val attachDonationRoll = rand.nextInt(100)
if (attachDonationRoll > 90) { // 10% chance of being added
val splitDonationRoll = rand.nextInt(100)
val donateAmount = (amount * randomDonatePct).toLong()
if (splitDonationRoll > 50) { // 50% chance of being split
// split
val split = genRandomDonationSplit(
1,
4
) // splits into at most 4 outputs, for a total of 6 outputs in the transaction (real dest + change. we don't add donations to send-all/sweep transactions)
val splitAmount = donateAmount / split
for (i in 0 until split) {
// TODO this can be expanded upon into the future to perform an auto-splitting/auto-churning for the user if their wallet is fresh and has few utxos.
// randomly split between multiple wallets
val randomDonationAddress = rand.nextInt(Constants.DONATION_ADDRESSES.size)
val donationAddress = Constants.DONATION_ADDRESSES[randomDonationAddress]
newOutputs.add(TransactionOutput(donationAddress, splitAmount))
}
} else {
// just add one output, for a total of 3 (real dest + change)
newOutputs.add(TransactionOutput(Constants.DONATE_ADDRESS, donateAmount))
}
val total = amount + donateAmount
checkSelectedAmounts(
preferredInputs,
total,
false
) // check that the selected UTXOs satisfy the new amount total
}
}
newOutputs.shuffle() // shuffle the outputs just in case. i think the monero library handles this for us anyway
return newOutputs
return wallet.createTransactionMultDest(outputs, feePriority, preferredInputs)
}
@Throws(Exception::class)
@ -240,16 +179,6 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
return pendingTx.commit("", true)
}
private fun getRandomDonateAmount(min: Float, max: Float): Float {
val rand = SecureRandom()
return rand.nextFloat() * (max - min) + min
}
private fun genRandomDonationSplit(min: Int, max: Int): Int {
val rand = SecureRandom()
return rand.nextInt(max) + min
}
interface Listener {
fun onRefresh(walletSynced: Boolean)
fun onConnectionFail()

View file

@ -126,7 +126,7 @@ class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
val destinations = ArrayList<Pair<String, Long>>()
destinations.add(
Pair(
"87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw",
Constants.DONATE_ADDRESS,
amount
)
)

View file

@ -11,7 +11,6 @@ object Constants {
const val PREF_USES_OFFSET = "pref_uses_offset"
const val PREF_STREET_MODE = "pref_street_mode"
const val PREF_MONEROCHAN = "pref_monerochan"
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
const val PREF_FROZEN_COINS = "pref_frozen_coins"
const val PREF_USE_BUNDLED_TOR = "pref_use_bundled_tor"
@ -23,11 +22,7 @@ object Constants {
const val DEFAULT_PREF_MONEROCHAN = false
val DONATION_ADDRESSES = arrayOf(
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address
"855acibBehTQJdC1f41BHWGq1FiQ5ztiAU7LiJgDUmmyJfDtRpJoo6Mc1en73duUScdeUYjLirACnZpv2C6pPbcZKgumdCS" // third Mysu Donation address
)
// Donation address is also specified in strings.xml, it is used as a tooltip in address fields
const val DONATE_ADDRESS =
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC"
}

View file

@ -87,68 +87,20 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/display_seed_button" />
<TextView
android:id="@+id/transaction_settings_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="24dp"
android:text="@string/transactions"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/display_utxos_button" />
<TextView
android:id="@+id/donate_per_tx_label_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="@string/option_donate_per_tx"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="@id/donate_per_tx_switch"
app:layout_constraintEnd_toStartOf="@id/donate_per_tx_switch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/donate_per_tx_switch" />
<TextView
android:id="@+id/donate_per_tx_desc_textview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:text="@string/option_donate_per_tx_desc"
android:textColor="@color/oled_addressListColor"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/donate_per_tx_switch" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/donate_per_tx_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:minWidth="48dp"
android:minHeight="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/transaction_settings_textview" />
<TextView
android:id="@+id/appearance_settings_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="32dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:text="@string/appearance"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/donate_per_tx_desc_textview" />
app:layout_constraintTop_toBottomOf="@+id/display_utxos_button" />
<TextView
android:id="@+id/street_mode_label_textview"

View file

@ -69,6 +69,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address_edittext" />
<TextView
android:id="@+id/donating_label_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginBottom="16dp"
android:text="@string/donating_label"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/amount_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address_edittext"
app:layout_constraintVertical_bias="1.0"
tools:visibility="visible" />
<ImageButton
android:id="@+id/paste_address_imagebutton"
android:layout_width="wrap_content"

View file

@ -5,6 +5,37 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/donate_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:text="@string/donate_label"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/amount_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/address_edittext"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/donating_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="16dp"
android:text="@string/donating_label"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@id/amount_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address_edittext"
app:layout_constraintVertical_bias="0.0"
tools:visibility="invisible" />
<EditText
android:id="@+id/amount_edittext"
android:layout_width="0dp"

View file

@ -35,12 +35,10 @@
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="street_mode">Street mode (hide balances)</string>
<string name="option_hide_xmrchan">Show Monerochan</string>
<string name="option_donate_per_tx">Add occasional donation</string>
<string name="option_donate_per_tx_desc" formatted="false">When enabled, there is a 10% chance when sending coins to add a 0.5%-3% donation to Mysu. These transaction fingerprints and donation amounts are randomized to preserve anonymity and privacy.</string>
<string name="display_recovery_phrase">Display wallet keys</string>
<string name="tor_switch_label">SOCKS Proxy</string>
<string name="connection_failed">Failed to connect. Retrying…</string>
<string name="address">87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw</string>
<string name="address">87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC</string>
<string name="amount">0.00</string>
<string name="sending_all">SENDING ALL</string>
<string name="send">Send</string>
@ -131,6 +129,7 @@
<string name="subbaddress_info_subtitle" translatable="false">#%1$d: %2$s</string>
<string name="previous_addresses">Previous addresses</string>
<string name="donate_label">Donate to Mysu</string>
<string name="donating_label">Donating to Mysu. Thank you!</string>
<string name="transactions">Transactions</string>
<string name="auth">[ auth ]</string>
<string name="trusted">[ trusted ]</string>