mirror of
https://codeberg.org/anoncontributorxmr/mysu.git
synced 2024-11-25 17:02:28 +00:00
Forgot to actually push the converted services
This commit is contained in:
parent
08b989eaab
commit
97d39a3cb4
18 changed files with 733 additions and 719 deletions
|
@ -1,49 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import net.mynero.wallet.data.Subaddress;
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class AddressService extends ServiceBase {
|
|
||||||
public static AddressService instance = null;
|
|
||||||
private int latestAddressIndex = 1;
|
|
||||||
|
|
||||||
public AddressService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AddressService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshAddresses() {
|
|
||||||
latestAddressIndex = WalletManager.getInstance().getWallet().getNumSubaddresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLatestAddressIndex() {
|
|
||||||
return latestAddressIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrimaryAddress() {
|
|
||||||
return WalletManager.getInstance().getWallet().getAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subaddress freshSubaddress() {
|
|
||||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date());
|
|
||||||
Wallet wallet = WalletManager.getInstance().getWallet();
|
|
||||||
wallet.addSubaddress(wallet.getAccountIndex(), timeStamp);
|
|
||||||
refreshAddresses();
|
|
||||||
wallet.store();
|
|
||||||
return wallet.getSubaddressObject(latestAddressIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subaddress currentSubaddress() {
|
|
||||||
Wallet wallet = WalletManager.getInstance().getWallet();
|
|
||||||
return wallet.getSubaddressObject(latestAddressIndex);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import net.mynero.wallet.data.Subaddress
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class AddressService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
|
var latestAddressIndex = 1
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshAddresses() {
|
||||||
|
WalletManager.instance?.wallet?.numSubaddresses?.let { latestAddressIndex = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun freshSubaddress(): Subaddress? {
|
||||||
|
val timeStamp = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(Date())
|
||||||
|
val wallet = WalletManager.instance?.wallet
|
||||||
|
wallet?.addSubaddress(wallet.getAccountIndex(), timeStamp)
|
||||||
|
refreshAddresses()
|
||||||
|
wallet?.store()
|
||||||
|
return wallet?.getSubaddressObject(latestAddressIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun currentSubaddress(): Subaddress? {
|
||||||
|
val wallet = WalletManager.instance?.wallet
|
||||||
|
return wallet?.getSubaddressObject(latestAddressIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
var instance: AddressService? = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.BalanceInfo;
|
|
||||||
import net.mynero.wallet.model.CoinsInfo;
|
|
||||||
|
|
||||||
public class BalanceService extends ServiceBase {
|
|
||||||
public static BalanceService instance = null;
|
|
||||||
private final MutableLiveData<BalanceInfo> _balanceInfo = new MutableLiveData<>(null);
|
|
||||||
public LiveData<BalanceInfo> balanceInfo = _balanceInfo;
|
|
||||||
|
|
||||||
public BalanceService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BalanceService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshBalance() {
|
|
||||||
long rawUnlocked = getUnlockedBalanceRaw();
|
|
||||||
long rawLocked = getLockedBalanceRaw();
|
|
||||||
_balanceInfo.postValue(new BalanceInfo(rawUnlocked, rawLocked));
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getUnlockedBalanceRaw() {
|
|
||||||
long unlocked = 0;
|
|
||||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
|
||||||
if (!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
|
||||||
unlocked += coinsInfo.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unlocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTotalBalanceRaw() {
|
|
||||||
long total = 0;
|
|
||||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
|
||||||
if (!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
|
||||||
total += coinsInfo.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLockedBalanceRaw() {
|
|
||||||
return getTotalBalanceRaw() - getUnlockedBalanceRaw();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.BalanceInfo
|
||||||
|
|
||||||
|
class BalanceService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
|
private val _balanceInfo = MutableLiveData<BalanceInfo?>(null)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var balanceInfo: LiveData<BalanceInfo?> = _balanceInfo
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshBalance() {
|
||||||
|
val rawUnlocked = unlockedBalanceRaw
|
||||||
|
val rawLocked = lockedBalanceRaw
|
||||||
|
_balanceInfo.postValue(BalanceInfo(rawUnlocked, rawLocked))
|
||||||
|
}
|
||||||
|
|
||||||
|
val unlockedBalanceRaw: Long
|
||||||
|
get() {
|
||||||
|
var unlocked: Long = 0
|
||||||
|
val utxos = UTXOService.instance?.getUtxos() ?: emptyList()
|
||||||
|
for (coinsInfo in utxos) {
|
||||||
|
if (!coinsInfo.isSpent && !coinsInfo.isFrozen && coinsInfo.isUnlocked && UTXOService.instance?.isCoinFrozen(
|
||||||
|
coinsInfo
|
||||||
|
) == false
|
||||||
|
) {
|
||||||
|
unlocked += coinsInfo.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unlocked
|
||||||
|
}
|
||||||
|
val totalBalanceRaw: Long
|
||||||
|
get() {
|
||||||
|
var total: Long = 0
|
||||||
|
val utxos = UTXOService.instance?.getUtxos() ?: emptyList()
|
||||||
|
for (coinsInfo in utxos) {
|
||||||
|
if (!coinsInfo.isSpent && !coinsInfo.isFrozen && UTXOService.instance?.isCoinFrozen(
|
||||||
|
coinsInfo
|
||||||
|
) == false
|
||||||
|
) {
|
||||||
|
total += coinsInfo.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
val lockedBalanceRaw: Long
|
||||||
|
get() = totalBalanceRaw - unlockedBalanceRaw
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
var instance: BalanceService? = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,55 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
|
|
||||||
public class BlockchainService extends ServiceBase {
|
|
||||||
public static BlockchainService instance = null;
|
|
||||||
private final MutableLiveData<Long> _currentHeight = new MutableLiveData<>(0L);
|
|
||||||
private final MutableLiveData<Wallet.ConnectionStatus> _connectionStatus = new MutableLiveData<>(Wallet.ConnectionStatus.ConnectionStatus_Disconnected);
|
|
||||||
public LiveData<Long> height = _currentHeight;
|
|
||||||
public LiveData<Wallet.ConnectionStatus> connectionStatus = _connectionStatus;
|
|
||||||
private long daemonHeight = 0;
|
|
||||||
private long lastDaemonHeightUpdateTimeMs = 0;
|
|
||||||
|
|
||||||
public BlockchainService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BlockchainService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshBlockchain() {
|
|
||||||
_currentHeight.postValue(getCurrentHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCurrentHeight() {
|
|
||||||
return WalletManager.getInstance().getWallet().getBlockChainHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDaemonHeight() {
|
|
||||||
return this.daemonHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDaemonHeight(long height) {
|
|
||||||
long t = System.currentTimeMillis();
|
|
||||||
if (height > 0) {
|
|
||||||
daemonHeight = height;
|
|
||||||
lastDaemonHeightUpdateTimeMs = t;
|
|
||||||
} else {
|
|
||||||
if (t - lastDaemonHeightUpdateTimeMs > 120000) {
|
|
||||||
daemonHeight = WalletManager.getInstance().getWallet().getDaemonBlockChainHeight();
|
|
||||||
lastDaemonHeightUpdateTimeMs = t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnectionStatus(Wallet.ConnectionStatus status) {
|
|
||||||
_connectionStatus.postValue(status);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.Wallet.ConnectionStatus
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
|
||||||
|
class BlockchainService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
|
private val _currentHeight = MutableLiveData(0L)
|
||||||
|
private val _connectionStatus = MutableLiveData(ConnectionStatus.ConnectionStatus_Disconnected)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var height: LiveData<Long> = _currentHeight
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var connectionStatus: LiveData<ConnectionStatus> = _connectionStatus
|
||||||
|
var daemonHeight: Long = 0
|
||||||
|
set(height) {
|
||||||
|
val t = System.currentTimeMillis()
|
||||||
|
if (height > 0) {
|
||||||
|
field = height
|
||||||
|
lastDaemonHeightUpdateTimeMs = t
|
||||||
|
} else {
|
||||||
|
if (t - lastDaemonHeightUpdateTimeMs > 120000) {
|
||||||
|
field = WalletManager.instance!!.wallet!!.getDaemonBlockChainHeight()
|
||||||
|
lastDaemonHeightUpdateTimeMs = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var lastDaemonHeightUpdateTimeMs: Long = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshBlockchain() {
|
||||||
|
_currentHeight.postValue(currentHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentHeight: Long
|
||||||
|
get() = WalletManager.instance!!.wallet!!.getBlockChainHeight()
|
||||||
|
|
||||||
|
fun setConnectionStatus(status: ConnectionStatus) {
|
||||||
|
_connectionStatus.postValue(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
var instance: BlockchainService? = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.TransactionInfo;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class HistoryService extends ServiceBase {
|
|
||||||
private static HistoryService instance = null;
|
|
||||||
private final MutableLiveData<List<TransactionInfo>> _history = new MutableLiveData<>();
|
|
||||||
public LiveData<List<TransactionInfo>> history = _history;
|
|
||||||
|
|
||||||
public HistoryService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HistoryService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshHistory() {
|
|
||||||
_history.postValue(getHistory());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TransactionInfo> getHistory() {
|
|
||||||
return WalletManager.getInstance().getWallet().getHistory().getAll();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.TransactionInfo
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
|
||||||
|
class HistoryService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
|
private val _history = MutableLiveData<List<TransactionInfo>>()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var history: LiveData<List<TransactionInfo>> = _history
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshHistory() {
|
||||||
|
_history.postValue(getHistory())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHistory(): List<TransactionInfo> {
|
||||||
|
return WalletManager.instance!!.wallet!!.history!!.all
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
var instance: HistoryService? = null
|
||||||
|
private set
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,238 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2006 The Android Open Source Project
|
|
||||||
* Copyright (c) 2017 m2049r
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import net.mynero.wallet.data.Node;
|
|
||||||
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;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import kotlin.Pair;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handy class for starting a new thread that has a looper. The looper can then be
|
|
||||||
* used to create handler classes. Note that start() must still be called.
|
|
||||||
* The started Thread has a stck size of STACK_SIZE (=5MB)
|
|
||||||
*/
|
|
||||||
public class MoneroHandlerThread extends Thread implements WalletListener {
|
|
||||||
// from src/cryptonote_config.h
|
|
||||||
static public final long THREAD_STACK_SIZE = 5 * 1024 * 1024;
|
|
||||||
private final Wallet wallet;
|
|
||||||
int triesLeft = 5;
|
|
||||||
private Listener listener = null;
|
|
||||||
|
|
||||||
public MoneroHandlerThread(String name, Listener listener, Wallet wallet) {
|
|
||||||
super(null, null, name, THREAD_STACK_SIZE);
|
|
||||||
this.listener = listener;
|
|
||||||
this.wallet = wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void start() {
|
|
||||||
super.start();
|
|
||||||
this.listener.onRefresh(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
PrefService prefService = PrefService.getInstance();
|
|
||||||
boolean usesTor = prefService.getBoolean(Constants.PREF_USES_TOR, false);
|
|
||||||
Node currentNode = prefService.getNode();
|
|
||||||
boolean isLocalIp = currentNode.getAddress().startsWith("10.") || currentNode.getAddress().startsWith("192.168.") || currentNode.getAddress().equals("localhost") || currentNode.getAddress().equals("127.0.0.1");
|
|
||||||
if (usesTor && !isLocalIp) {
|
|
||||||
String proxy = prefService.getProxy();
|
|
||||||
WalletManager.getInstance().setProxy(proxy);
|
|
||||||
wallet.setProxy(proxy);
|
|
||||||
}
|
|
||||||
WalletManager.getInstance().setDaemon(currentNode);
|
|
||||||
wallet.init(0);
|
|
||||||
wallet.setListener(this);
|
|
||||||
wallet.startRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moneySpent(String txId, long amount) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void moneyReceived(String txId, long amount) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unconfirmedMoneyReceived(String txId, long amount) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void newBlock(long height) {
|
|
||||||
refresh(false);
|
|
||||||
BlockchainService.getInstance().setDaemonHeight(wallet.isSynchronized() ? height : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updated() {
|
|
||||||
refresh(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refreshed() {
|
|
||||||
Wallet.ConnectionStatus status = wallet.getFullStatus().connectionStatus;
|
|
||||||
if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
|
|
||||||
if (triesLeft > 0) {
|
|
||||||
wallet.startRefresh();
|
|
||||||
triesLeft--;
|
|
||||||
} else {
|
|
||||||
listener.onConnectionFail();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BlockchainService.getInstance().setDaemonHeight(wallet.getDaemonBlockChainHeight());
|
|
||||||
wallet.setSynchronized();
|
|
||||||
wallet.store();
|
|
||||||
refresh(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockchainService.getInstance().setConnectionStatus(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh(boolean walletSynced) {
|
|
||||||
wallet.refreshHistory();
|
|
||||||
if (walletSynced) {
|
|
||||||
wallet.refreshCoins();
|
|
||||||
}
|
|
||||||
listener.onRefresh(walletSynced);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction createTx(String address, String amountStr, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
|
|
||||||
ArrayList<Pair<String, String>> dests = new ArrayList<>();
|
|
||||||
dests.add(new Pair(address, amountStr));
|
|
||||||
return createTx(dests, sendAll, feePriority, selectedUtxos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction createTx(List<Pair<String, String>> dests, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
|
|
||||||
long totalAmount = 0;
|
|
||||||
ArrayList<TransactionOutput> outputs = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Pair<String, String> dest : dests) {
|
|
||||||
long amount = Wallet.getAmountFromString(dest.component2());
|
|
||||||
totalAmount += amount;
|
|
||||||
outputs.add(new TransactionOutput(dest.component1(), amount));
|
|
||||||
}
|
|
||||||
ArrayList<String> preferredInputs;
|
|
||||||
if (selectedUtxos.isEmpty()) {
|
|
||||||
// no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app
|
|
||||||
preferredInputs = UTXOService.getInstance().selectUtxos(totalAmount, sendAll, feePriority);
|
|
||||||
} else {
|
|
||||||
preferredInputs = selectedUtxos;
|
|
||||||
checkSelectedAmounts(preferredInputs, totalAmount, sendAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendAll) {
|
|
||||||
Pair<String, String> dest = dests.get(0);
|
|
||||||
String address = dest.component1();
|
|
||||||
return wallet.createSweepTransaction(address, feePriority, preferredInputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TransactionOutput> finalOutputs = maybeAddDonationOutputs(totalAmount, outputs, preferredInputs);
|
|
||||||
return wallet.createTransactionMultDest(finalOutputs, feePriority, preferredInputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TransactionOutput> maybeAddDonationOutputs(long amount, List<TransactionOutput> outputs, List<String> preferredInputs) throws Exception {
|
|
||||||
TransactionOutput mainDestination = outputs.get(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
|
|
||||||
String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.destination, WalletManager.getInstance().networkType.value);
|
|
||||||
ArrayList<TransactionOutput> newOutputs = new ArrayList<>(outputs);
|
|
||||||
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false);
|
|
||||||
if (donatePerTx && paymentId.isEmpty()) { // only attach donation when no payment id is needed (i.e. integrated address)
|
|
||||||
SecureRandom rand = new SecureRandom();
|
|
||||||
float randomDonatePct = getRandomDonateAmount(0.005f, 0.015f); // occasionally attaches a 0.5% to 1.5% 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.
|
|
||||||
*/
|
|
||||||
int attachDonationRoll = rand.nextInt(100);
|
|
||||||
if (attachDonationRoll > 90) { // 10% chance of being added
|
|
||||||
int splitDonationRoll = rand.nextInt(100);
|
|
||||||
long donateAmount = (long) (amount * randomDonatePct);
|
|
||||||
if (splitDonationRoll > 50) { // 50% chance of being split
|
|
||||||
// split
|
|
||||||
int 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)
|
|
||||||
long splitAmount = donateAmount / split;
|
|
||||||
for (int i = 0; i < split; i++) {
|
|
||||||
// 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
|
|
||||||
int randomDonationAddress = rand.nextInt(Constants.DONATION_ADDRESSES.length);
|
|
||||||
String donationAddress = Constants.DONATION_ADDRESSES[randomDonationAddress];
|
|
||||||
newOutputs.add(new TransactionOutput(donationAddress, splitAmount));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// just add one output, for a total of 3 (real dest + change)
|
|
||||||
newOutputs.add(new TransactionOutput(Constants.DONATE_ADDRESS, donateAmount));
|
|
||||||
}
|
|
||||||
long total = amount + donateAmount;
|
|
||||||
checkSelectedAmounts(preferredInputs, total, false); // check that the selected UTXOs satisfy the new amount total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.shuffle(newOutputs); // shuffle the outputs just in case. i think the monero library handles this for us anyway
|
|
||||||
|
|
||||||
return newOutputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkSelectedAmounts(List<String> selectedUtxos, long amount, boolean sendAll) throws Exception {
|
|
||||||
if (!sendAll) {
|
|
||||||
long amountSelected = 0;
|
|
||||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
|
||||||
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
|
||||||
amountSelected += coinsInfo.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountSelected <= amount) {
|
|
||||||
throw new Exception("insufficient wallet balance");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sendTx(PendingTransaction pendingTx) {
|
|
||||||
return pendingTx.commit("", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getRandomDonateAmount(float min, float max) {
|
|
||||||
SecureRandom rand = new SecureRandom();
|
|
||||||
return rand.nextFloat() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int genRandomDonationSplit(int min, int max) {
|
|
||||||
SecureRandom rand = new SecureRandom();
|
|
||||||
return rand.nextInt(max) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
void onRefresh(boolean walletSynced);
|
|
||||||
|
|
||||||
void onConnectionFail();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2006 The Android Open Source Project
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
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
|
||||||
|
* used to create handler classes. Note that start() must still be called.
|
||||||
|
* The started Thread has a stck size of STACK_SIZE (=5MB)
|
||||||
|
*/
|
||||||
|
class MoneroHandlerThread(name: String?, val listener: Listener?, wallet: Wallet) :
|
||||||
|
Thread(null, null, name, THREAD_STACK_SIZE), WalletListener {
|
||||||
|
private val wallet: Wallet
|
||||||
|
var triesLeft = 5
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.wallet = wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
listener?.onRefresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val prefService = PrefService.instance ?: return
|
||||||
|
val usesTor = prefService.getBoolean(Constants.PREF_USES_TOR, false)
|
||||||
|
val currentNode = prefService.node
|
||||||
|
val isLocalIp =
|
||||||
|
currentNode?.address?.startsWith("10.") == true ||
|
||||||
|
currentNode?.address?.startsWith("192.168.") == true ||
|
||||||
|
currentNode?.address == "localhost" ||
|
||||||
|
currentNode?.address == "127.0.0.1"
|
||||||
|
if (usesTor && !isLocalIp) {
|
||||||
|
val proxy = prefService.proxy
|
||||||
|
proxy?.let { WalletManager.instance?.setProxy(it) }
|
||||||
|
wallet.setProxy(proxy)
|
||||||
|
}
|
||||||
|
WalletManager.instance?.setDaemon(currentNode)
|
||||||
|
wallet.init(0)
|
||||||
|
wallet.setListener(this)
|
||||||
|
wallet.startRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun moneySpent(txId: String?, amount: Long) {}
|
||||||
|
override fun moneyReceived(txId: String?, amount: Long) {}
|
||||||
|
override fun unconfirmedMoneyReceived(txId: String?, amount: Long) {}
|
||||||
|
override fun newBlock(height: Long) {
|
||||||
|
refresh(false)
|
||||||
|
BlockchainService.instance?.daemonHeight = if (wallet.isSynchronized) height else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updated() {
|
||||||
|
refresh(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refreshed() {
|
||||||
|
val status = wallet.fullStatus.connectionStatus
|
||||||
|
if (status === ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
|
||||||
|
if (triesLeft > 0) {
|
||||||
|
wallet.startRefresh()
|
||||||
|
triesLeft--
|
||||||
|
} else {
|
||||||
|
listener?.onConnectionFail()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BlockchainService.instance?.daemonHeight = wallet.getDaemonBlockChainHeight()
|
||||||
|
wallet.setSynchronized()
|
||||||
|
wallet.store()
|
||||||
|
refresh(true)
|
||||||
|
}
|
||||||
|
status?.let { BlockchainService.instance?.setConnectionStatus(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refresh(walletSynced: Boolean) {
|
||||||
|
wallet.refreshHistory()
|
||||||
|
if (walletSynced) {
|
||||||
|
wallet.refreshCoins()
|
||||||
|
}
|
||||||
|
listener?.onRefresh(walletSynced)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun createTx(
|
||||||
|
address: String,
|
||||||
|
amountStr: String,
|
||||||
|
sendAll: Boolean,
|
||||||
|
feePriority: PendingTransaction.Priority,
|
||||||
|
selectedUtxos: ArrayList<String>
|
||||||
|
): PendingTransaction? {
|
||||||
|
val dests = ArrayList<Pair<String, String>>()
|
||||||
|
dests.add(Pair(address, amountStr))
|
||||||
|
return createTx(dests, sendAll, feePriority, selectedUtxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun createTx(
|
||||||
|
dests: List<Pair<String, String>>,
|
||||||
|
sendAll: Boolean,
|
||||||
|
feePriority: PendingTransaction.Priority,
|
||||||
|
selectedUtxos: ArrayList<String>
|
||||||
|
): PendingTransaction? {
|
||||||
|
var totalAmount: Long = 0
|
||||||
|
val outputs = ArrayList<TransactionOutput>()
|
||||||
|
for (dest in dests) {
|
||||||
|
val amount = getAmountFromString(dest.component2())
|
||||||
|
totalAmount += amount
|
||||||
|
outputs.add(TransactionOutput(dest.component1(), amount))
|
||||||
|
}
|
||||||
|
val preferredInputs: ArrayList<String>
|
||||||
|
if (selectedUtxos.isEmpty()) {
|
||||||
|
// no inputs manually selected, we are sending from home screen most likely, or user somehow broke the app
|
||||||
|
preferredInputs =
|
||||||
|
UTXOService.instance?.selectUtxos(totalAmount, sendAll, feePriority) ?: ArrayList()
|
||||||
|
} else {
|
||||||
|
preferredInputs = selectedUtxos
|
||||||
|
checkSelectedAmounts(preferredInputs, totalAmount, sendAll)
|
||||||
|
}
|
||||||
|
if (sendAll) {
|
||||||
|
val dest = dests[0]
|
||||||
|
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.015f
|
||||||
|
) // occasionally attaches a 0.5% to 1.5% 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
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
private fun checkSelectedAmounts(selectedUtxos: List<String?>, amount: Long, sendAll: Boolean) {
|
||||||
|
if (!sendAll) {
|
||||||
|
var amountSelected: Long = 0
|
||||||
|
val utxos = UTXOService.instance?.getUtxos() ?: emptyList()
|
||||||
|
for (coinsInfo in utxos) {
|
||||||
|
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
||||||
|
amountSelected += coinsInfo.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amountSelected <= amount) {
|
||||||
|
throw Exception("insufficient wallet balance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendTx(pendingTx: PendingTransaction): Boolean {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// from src/cryptonote_config.h
|
||||||
|
const val THREAD_STACK_SIZE = (5 * 1024 * 1024).toLong()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,110 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
|
|
||||||
import net.mynero.wallet.MoneroApplication;
|
|
||||||
import net.mynero.wallet.data.DefaultNodes;
|
|
||||||
import net.mynero.wallet.data.Node;
|
|
||||||
import net.mynero.wallet.util.Constants;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
public class PrefService extends ServiceBase {
|
|
||||||
private static SharedPreferences preferences = null;
|
|
||||||
private static PrefService instance = null;
|
|
||||||
|
|
||||||
public PrefService(MoneroApplication application) {
|
|
||||||
super(null);
|
|
||||||
preferences = application.getSharedPreferences(application.getApplicationInfo().packageName, Context.MODE_PRIVATE);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PrefService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SharedPreferences.Editor edit() {
|
|
||||||
return preferences.edit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Node getNode() {
|
|
||||||
boolean usesProxy = getBoolean(Constants.PREF_USES_TOR, false);
|
|
||||||
DefaultNodes defaultNode = DefaultNodes.SAMOURAI;
|
|
||||||
if (usesProxy) {
|
|
||||||
String proxyPort = getProxyPort();
|
|
||||||
if (!proxyPort.isEmpty()) {
|
|
||||||
int port = Integer.parseInt(proxyPort);
|
|
||||||
if (port == 4447) {
|
|
||||||
defaultNode = DefaultNodes.MYNERO_I2P;
|
|
||||||
} else {
|
|
||||||
defaultNode = DefaultNodes.MYNERO_ONION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String nodeString = getString(Constants.PREF_NODE_2, defaultNode.getNodeString());
|
|
||||||
try {
|
|
||||||
JSONObject nodeJson = new JSONObject(nodeString);
|
|
||||||
return Node.fromJson(nodeJson);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
// stored node is not json format, upgrade if possible
|
|
||||||
return upgradeOldNode(nodeString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Node upgradeOldNode(String nodeString) {
|
|
||||||
if (!nodeString.isEmpty()) {
|
|
||||||
Node node = Node.fromString(nodeString);
|
|
||||||
if (node != null) {
|
|
||||||
edit().putString(Constants.PREF_NODE_2, node.toJson().toString()).apply();
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProxy() {
|
|
||||||
return PrefService.getInstance().getString(Constants.PREF_PROXY, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasProxySet() {
|
|
||||||
String proxyString = getProxy();
|
|
||||||
return proxyString.contains(":");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProxyAddress() {
|
|
||||||
if (hasProxySet()) {
|
|
||||||
String proxyString = getProxy();
|
|
||||||
return proxyString.split(":")[0];
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getProxyPort() {
|
|
||||||
if (hasProxySet()) {
|
|
||||||
String proxyString = getProxy();
|
|
||||||
return proxyString.split(":")[1];
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getString(String key, String defaultValue) {
|
|
||||||
String value = preferences.getString(key, "");
|
|
||||||
if (value.isEmpty() && !defaultValue.isEmpty()) {
|
|
||||||
edit().putString(key, defaultValue).apply();
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getBoolean(String key, boolean defaultValue) {
|
|
||||||
boolean containsKey = preferences.contains(key);
|
|
||||||
boolean value = preferences.getBoolean(key, false);
|
|
||||||
if (!value && defaultValue && !containsKey) {
|
|
||||||
edit().putBoolean(key, true).apply();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
118
app/src/main/java/net/mynero/wallet/service/PrefService.kt
Normal file
118
app/src/main/java/net/mynero/wallet/service/PrefService.kt
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import net.mynero.wallet.MoneroApplication
|
||||||
|
import net.mynero.wallet.data.DefaultNodes
|
||||||
|
import net.mynero.wallet.data.Node
|
||||||
|
import net.mynero.wallet.data.Node.Companion.fromJson
|
||||||
|
import net.mynero.wallet.data.Node.Companion.fromString
|
||||||
|
import net.mynero.wallet.util.Constants
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
class PrefService(application: MoneroApplication) : ServiceBase(null) {
|
||||||
|
init {
|
||||||
|
preferences = application.getSharedPreferences(
|
||||||
|
application.applicationInfo.packageName,
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun edit(): SharedPreferences.Editor? {
|
||||||
|
return preferences?.edit()
|
||||||
|
}
|
||||||
|
|
||||||
|
val node: Node?
|
||||||
|
get() {
|
||||||
|
val usesProxy = getBoolean(Constants.PREF_USES_TOR, false)
|
||||||
|
var defaultNode = DefaultNodes.SAMOURAI
|
||||||
|
if (usesProxy) {
|
||||||
|
val proxyPort = proxyPort
|
||||||
|
if (proxyPort.isNotEmpty()) {
|
||||||
|
val port = proxyPort.toInt()
|
||||||
|
defaultNode = if (port == 4447) {
|
||||||
|
DefaultNodes.MYNERO_I2P
|
||||||
|
} else {
|
||||||
|
DefaultNodes.MYNERO_ONION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nodeString = getString(Constants.PREF_NODE_2, defaultNode.nodeString)
|
||||||
|
return try {
|
||||||
|
val nodeJson = nodeString?.let { JSONObject(it) }
|
||||||
|
fromJson(nodeJson)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
// stored node is not json format, upgrade if possible
|
||||||
|
nodeString?.let { upgradeOldNode(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun upgradeOldNode(nodeString: String): Node? {
|
||||||
|
if (nodeString.isNotEmpty()) {
|
||||||
|
val node = fromString(nodeString)
|
||||||
|
if (node != null) {
|
||||||
|
edit()?.putString(Constants.PREF_NODE_2, node.toJson().toString())?.apply()
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val proxy: String?
|
||||||
|
get() = instance?.getString(Constants.PREF_PROXY, "")
|
||||||
|
|
||||||
|
fun hasProxySet(): Boolean {
|
||||||
|
val proxyString = proxy
|
||||||
|
return proxyString?.contains(":") == true
|
||||||
|
}
|
||||||
|
|
||||||
|
val proxyAddress: String
|
||||||
|
get() {
|
||||||
|
if (hasProxySet()) {
|
||||||
|
val proxyString = proxy
|
||||||
|
return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() }
|
||||||
|
?.toTypedArray()
|
||||||
|
?.get(0) ?: ""
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
val proxyPort: String
|
||||||
|
get() {
|
||||||
|
if (hasProxySet()) {
|
||||||
|
val proxyString = proxy
|
||||||
|
return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() }
|
||||||
|
?.toTypedArray()
|
||||||
|
?.get(1) ?: ""
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getString(key: String?, defaultValue: String): String? {
|
||||||
|
val value = preferences?.getString(key, "")
|
||||||
|
if (value?.isEmpty() == true && defaultValue.isNotEmpty()) {
|
||||||
|
edit()?.putString(key, defaultValue)?.apply()
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBoolean(key: String?, defaultValue: Boolean): Boolean {
|
||||||
|
val containsKey = preferences?.contains(key)
|
||||||
|
val value = preferences?.getBoolean(key, false)
|
||||||
|
if (value == false && defaultValue && containsKey == false) {
|
||||||
|
edit()?.putBoolean(key, true)?.apply()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return value == true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var preferences: SharedPreferences? = null
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
var instance: PrefService? = null
|
||||||
|
private set
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
public class ServiceBase {
|
|
||||||
private final MoneroHandlerThread thread;
|
|
||||||
|
|
||||||
public ServiceBase(MoneroHandlerThread thread) {
|
|
||||||
this.thread = thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroHandlerThread getThread() {
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
open class ServiceBase(@JvmField val thread: MoneroHandlerThread?)
|
|
@ -1,33 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.PendingTransaction;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import kotlin.Pair;
|
|
||||||
|
|
||||||
public class TxService extends ServiceBase {
|
|
||||||
public static TxService instance = null;
|
|
||||||
|
|
||||||
public TxService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TxService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction createTx(String address, String amount, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
|
|
||||||
return this.getThread().createTx(address, amount, sendAll, feePriority, selectedUtxos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction createTx(List<Pair<String, String>> dests, boolean sendAll, PendingTransaction.Priority feePriority, ArrayList<String> selectedUtxos) throws Exception {
|
|
||||||
return this.getThread().createTx(dests, sendAll, feePriority, selectedUtxos);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean sendTx(PendingTransaction pendingTransaction) {
|
|
||||||
return this.getThread().sendTx(pendingTransaction);
|
|
||||||
}
|
|
||||||
}
|
|
39
app/src/main/java/net/mynero/wallet/service/TxService.kt
Normal file
39
app/src/main/java/net/mynero/wallet/service/TxService.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import net.mynero.wallet.model.PendingTransaction
|
||||||
|
|
||||||
|
class TxService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun createTx(
|
||||||
|
address: String,
|
||||||
|
amount: String,
|
||||||
|
sendAll: Boolean,
|
||||||
|
feePriority: PendingTransaction.Priority,
|
||||||
|
selectedUtxos: ArrayList<String>
|
||||||
|
): PendingTransaction? {
|
||||||
|
return thread?.createTx(address, amount, sendAll, feePriority, selectedUtxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun createTx(
|
||||||
|
dests: List<Pair<String, String>>,
|
||||||
|
sendAll: Boolean,
|
||||||
|
feePriority: PendingTransaction.Priority,
|
||||||
|
selectedUtxos: ArrayList<String>
|
||||||
|
): PendingTransaction? {
|
||||||
|
return thread?.createTx(dests, sendAll, feePriority, selectedUtxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendTx(pendingTransaction: PendingTransaction): Boolean {
|
||||||
|
return thread?.sendTx(pendingTransaction) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
var instance: TxService? = null
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,137 +0,0 @@
|
||||||
package net.mynero.wallet.service;
|
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.CoinsInfo;
|
|
||||||
import net.mynero.wallet.model.PendingTransaction;
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
import net.mynero.wallet.util.Constants;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class UTXOService extends ServiceBase {
|
|
||||||
public static UTXOService instance = null;
|
|
||||||
private final MutableLiveData<List<CoinsInfo>> _utxos = new MutableLiveData<>();
|
|
||||||
public LiveData<List<CoinsInfo>> utxos = _utxos;
|
|
||||||
private List<CoinsInfo> internalCachedUtxos = new ArrayList<>();
|
|
||||||
private ArrayList<String> frozenCoins = new ArrayList<>();
|
|
||||||
|
|
||||||
public UTXOService(MoneroHandlerThread thread) {
|
|
||||||
super(thread);
|
|
||||||
instance = this;
|
|
||||||
try {
|
|
||||||
this.loadFrozenCoins();
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UTXOService getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshUtxos() {
|
|
||||||
List<CoinsInfo> coinsInfos = getUtxosInternal();
|
|
||||||
_utxos.postValue(coinsInfos);
|
|
||||||
internalCachedUtxos = coinsInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<CoinsInfo> getUtxos() {
|
|
||||||
return Collections.unmodifiableList(internalCachedUtxos);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CoinsInfo> getUtxosInternal() {
|
|
||||||
return WalletManager.getInstance().getWallet().getCoins().getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) {
|
|
||||||
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
|
|
||||||
for (CoinsInfo coin : selectedCoins.values()) {
|
|
||||||
if (frozenCoinsCopy.contains(coin.pubKey)) {
|
|
||||||
frozenCoinsCopy.remove(coin.pubKey);
|
|
||||||
} else {
|
|
||||||
frozenCoinsCopy.add(coin.pubKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.frozenCoins = frozenCoinsCopy;
|
|
||||||
this.saveFrozenCoins();
|
|
||||||
refreshUtxos();
|
|
||||||
BalanceService.getInstance().refreshBalance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCoinFrozen(CoinsInfo coinsInfo) {
|
|
||||||
return frozenCoins.contains(coinsInfo.pubKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFrozenCoins() throws JSONException {
|
|
||||||
PrefService prefService = PrefService.getInstance();
|
|
||||||
String frozenCoinsArrayString = prefService.getString(Constants.PREF_FROZEN_COINS, "[]");
|
|
||||||
JSONArray frozenCoinsArray = new JSONArray(frozenCoinsArrayString);
|
|
||||||
for (int i = 0; i < frozenCoinsArray.length(); i++) {
|
|
||||||
String pubKey = frozenCoinsArray.getString(i);
|
|
||||||
frozenCoins.add(pubKey);
|
|
||||||
}
|
|
||||||
this.refreshUtxos();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveFrozenCoins() {
|
|
||||||
PrefService prefService = PrefService.getInstance();
|
|
||||||
JSONArray jsonArray = new JSONArray();
|
|
||||||
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
|
|
||||||
for (String pubKey : frozenCoinsCopy) {
|
|
||||||
jsonArray.put(pubKey);
|
|
||||||
}
|
|
||||||
prefService.edit().putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> selectUtxos(long amount, boolean sendAll, PendingTransaction.Priority feePriority) throws Exception {
|
|
||||||
final long basicFeeEstimate = calculateBasicFee(amount, feePriority);
|
|
||||||
final long amountWithBasicFee = amount + basicFeeEstimate;
|
|
||||||
ArrayList<String> selectedUtxos = new ArrayList<>();
|
|
||||||
ArrayList<String> seenTxs = new ArrayList<>();
|
|
||||||
List<CoinsInfo> utxos = new ArrayList<>(getUtxos());
|
|
||||||
long amountSelected = 0;
|
|
||||||
Collections.sort(utxos);
|
|
||||||
//loop through each utxo
|
|
||||||
for (CoinsInfo coinsInfo : utxos) {
|
|
||||||
if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen() && !frozenCoins.contains(coinsInfo.pubKey)) { //filter out spent, locked, and frozen outputs
|
|
||||||
if (sendAll) {
|
|
||||||
// if send all, add all utxos and set amount to send all
|
|
||||||
selectedUtxos.add(coinsInfo.keyImage);
|
|
||||||
amountSelected = Wallet.SWEEP_ALL;
|
|
||||||
} else {
|
|
||||||
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo
|
|
||||||
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) {
|
|
||||||
selectedUtxos.add(coinsInfo.keyImage);
|
|
||||||
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
|
|
||||||
seenTxs.add(coinsInfo.hash);
|
|
||||||
amountSelected += coinsInfo.amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amountSelected < amountWithBasicFee && !sendAll) {
|
|
||||||
throw new Exception("insufficient wallet balance");
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedUtxos;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long calculateBasicFee(long amount, PendingTransaction.Priority feePriority) {
|
|
||||||
ArrayList<Pair<String, Long>> destinations = new ArrayList<>();
|
|
||||||
destinations.add(new Pair<>("87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw", amount));
|
|
||||||
// destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter
|
|
||||||
return WalletManager.getInstance().getWallet().estimateTransactionFee(destinations, feePriority);
|
|
||||||
}
|
|
||||||
}
|
|
144
app/src/main/java/net/mynero/wallet/service/UTXOService.kt
Normal file
144
app/src/main/java/net/mynero/wallet/service/UTXOService.kt
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import android.util.Pair
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.CoinsInfo
|
||||||
|
import net.mynero.wallet.model.PendingTransaction
|
||||||
|
import net.mynero.wallet.model.Wallet
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
import net.mynero.wallet.util.Constants
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONException
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
class UTXOService(thread: MoneroHandlerThread?) : ServiceBase(thread) {
|
||||||
|
private val _utxos = MutableLiveData<List<CoinsInfo>>()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
var utxos: LiveData<List<CoinsInfo>> = _utxos
|
||||||
|
private var internalCachedUtxos: List<CoinsInfo> = ArrayList()
|
||||||
|
private var frozenCoins = ArrayList<String?>()
|
||||||
|
val utxosInternal: List<CoinsInfo>
|
||||||
|
get() {
|
||||||
|
return WalletManager.instance?.wallet?.coins?.all ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
try {
|
||||||
|
loadFrozenCoins()
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshUtxos() {
|
||||||
|
val coinsInfos: List<CoinsInfo> = this.utxosInternal
|
||||||
|
_utxos.postValue(coinsInfos)
|
||||||
|
internalCachedUtxos = coinsInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUtxos(): List<CoinsInfo> {
|
||||||
|
return Collections.unmodifiableList(internalCachedUtxos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleFrozen(selectedCoins: HashMap<String?, CoinsInfo>) {
|
||||||
|
val frozenCoinsCopy = ArrayList(frozenCoins)
|
||||||
|
for (coin in selectedCoins.values) {
|
||||||
|
if (frozenCoinsCopy.contains(coin.pubKey)) {
|
||||||
|
frozenCoinsCopy.remove(coin.pubKey)
|
||||||
|
} else {
|
||||||
|
frozenCoinsCopy.add(coin.pubKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frozenCoins = frozenCoinsCopy
|
||||||
|
saveFrozenCoins()
|
||||||
|
refreshUtxos()
|
||||||
|
BalanceService.instance!!.refreshBalance()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCoinFrozen(coinsInfo: CoinsInfo): Boolean {
|
||||||
|
return frozenCoins.contains(coinsInfo.pubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JSONException::class)
|
||||||
|
private fun loadFrozenCoins() {
|
||||||
|
val prefService = PrefService.instance
|
||||||
|
val frozenCoinsArrayString = prefService!!.getString(Constants.PREF_FROZEN_COINS, "[]")
|
||||||
|
val frozenCoinsArray = JSONArray(frozenCoinsArrayString)
|
||||||
|
for (i in 0 until frozenCoinsArray.length()) {
|
||||||
|
val pubKey = frozenCoinsArray.getString(i)
|
||||||
|
frozenCoins.add(pubKey)
|
||||||
|
}
|
||||||
|
refreshUtxos()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveFrozenCoins() {
|
||||||
|
val prefService = PrefService.instance
|
||||||
|
val jsonArray = JSONArray()
|
||||||
|
val frozenCoinsCopy = ArrayList(frozenCoins)
|
||||||
|
for (pubKey in frozenCoinsCopy) {
|
||||||
|
jsonArray.put(pubKey)
|
||||||
|
}
|
||||||
|
prefService!!.edit()!!
|
||||||
|
.putString(Constants.PREF_FROZEN_COINS, jsonArray.toString()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun selectUtxos(
|
||||||
|
amount: Long,
|
||||||
|
sendAll: Boolean,
|
||||||
|
feePriority: PendingTransaction.Priority
|
||||||
|
): ArrayList<String> {
|
||||||
|
val basicFeeEstimate = calculateBasicFee(amount, feePriority)
|
||||||
|
val amountWithBasicFee = amount + basicFeeEstimate
|
||||||
|
val selectedUtxos = ArrayList<String>()
|
||||||
|
val seenTxs = ArrayList<String>()
|
||||||
|
val utxos: List<CoinsInfo> = ArrayList(getUtxos())
|
||||||
|
var amountSelected: Long = 0
|
||||||
|
utxos.sorted()
|
||||||
|
//loop through each utxo
|
||||||
|
for (coinsInfo in utxos) {
|
||||||
|
if (!coinsInfo.isSpent && coinsInfo.isUnlocked && !coinsInfo.isFrozen && !frozenCoins.contains(
|
||||||
|
coinsInfo.pubKey
|
||||||
|
)
|
||||||
|
) { //filter out spent, locked, and frozen outputs
|
||||||
|
if (sendAll) {
|
||||||
|
// if send all, add all utxos and set amount to send all
|
||||||
|
coinsInfo.keyImage?.let { selectedUtxos.add(it) }
|
||||||
|
amountSelected = Wallet.SWEEP_ALL
|
||||||
|
} else {
|
||||||
|
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo
|
||||||
|
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) {
|
||||||
|
coinsInfo.keyImage?.let { selectedUtxos.add(it) }
|
||||||
|
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
|
||||||
|
coinsInfo.hash?.let { seenTxs.add(it) }
|
||||||
|
amountSelected += coinsInfo.amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (amountSelected < amountWithBasicFee && !sendAll) {
|
||||||
|
throw Exception("insufficient wallet balance")
|
||||||
|
}
|
||||||
|
return selectedUtxos
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateBasicFee(amount: Long, feePriority: PendingTransaction.Priority): Long {
|
||||||
|
val destinations = ArrayList<Pair<String, Long>>()
|
||||||
|
destinations.add(
|
||||||
|
Pair(
|
||||||
|
"87MRtZPrWUCVUgcFHdsVb5MoZUcLtqfD3FvQVGwftFb8eSdMnE39JhAJcbuSW8X2vRaRsB9RQfuCpFciybJFHaz3QYPhCLw",
|
||||||
|
amount
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// destination string doesn't actually matter here, so i'm using the donation address. amount also technically doesn't matter
|
||||||
|
return WalletManager.instance!!.wallet!!.estimateTransactionFee(destinations, feePriority)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
var instance: UTXOService? = null
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue