Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement sending custom unspent outputs for BTC based blockchains #6951

Merged
merged 3 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ dependencies {

// Wallet kits
implementation 'com.github.horizontalsystems:ton-kit-kmm:18bc110'
implementation 'com.github.horizontalsystems:bitcoin-kit-android:7ef3770'
implementation 'com.github.horizontalsystems:bitcoin-kit-android:4956962'
implementation 'com.github.horizontalsystems:ethereum-kit-android:3a02f3a'
implementation 'com.github.horizontalsystems:blockchain-fee-rate-kit-android:1d3bd49'
implementation 'com.github.horizontalsystems:binance-chain-kit-android:c1509a2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.horizontalsystems.bankwallet.core

import android.os.Parcelable
import com.google.gson.JsonObject
import io.horizontalsystems.bankwallet.core.adapters.BitcoinFeeInfo
import io.horizontalsystems.bankwallet.core.adapters.zcash.ZcashAdapter
import io.horizontalsystems.bankwallet.core.managers.ActiveAccountState
import io.horizontalsystems.bankwallet.core.managers.Bep2TokenInfoService
Expand Down Expand Up @@ -37,6 +38,7 @@ import io.horizontalsystems.bankwallet.modules.theme.ThemeType
import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType
import io.horizontalsystems.binancechainkit.BinanceChainKit
import io.horizontalsystems.bitcoincore.core.IPluginData
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.ethereumkit.models.Address
import io.horizontalsystems.ethereumkit.models.TransactionData
import io.horizontalsystems.marketkit.models.BlockchainType
Expand Down Expand Up @@ -115,6 +117,9 @@ interface ILocalStorage {
var personalSupportEnabled: Boolean
var hideSuspiciousTransactions: Boolean
var pinRandomized: Boolean
var utxoExpertModeEnabled: Boolean

val utxoExpertModeEnabledFlow: StateFlow<Boolean>

fun getSwapProviderId(blockchainType: BlockchainType): String?
fun setSwapProviderId(blockchainType: BlockchainType, providerId: String)
Expand Down Expand Up @@ -289,8 +294,9 @@ interface IReceiveAdapter {
val isAccountActive: Boolean
get() = true

val usedAddresses: List<UsedAddress>
get() = listOf()
fun usedAddresses(change: Boolean): List<UsedAddress> {
return listOf()
}
}

@Parcelize
Expand All @@ -301,27 +307,31 @@ data class UsedAddress(
): Parcelable

interface ISendBitcoinAdapter {
val unspentOutputs: List<UnspentOutputInfo>
val balanceData: BalanceData
val blockchainType: BlockchainType
fun availableBalance(
feeRate: Int,
address: String?,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?
): BigDecimal

fun minimumSendAmount(address: String?): BigDecimal?
fun fee(
fun bitcoinFeeInfo(
amount: BigDecimal,
feeRate: Int,
address: String?,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?
): BigDecimal?
): BitcoinFeeInfo?

fun validate(address: String, pluginData: Map<Byte, IPluginData>?)
fun send(
amount: BigDecimal,
address: String,
feeRate: Int,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?,
transactionSorting: TransactionDataSortMode?,
logger: AppLogger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.bitcoinkit.BitcoinKit
import io.horizontalsystems.bitcoinkit.BitcoinKit.NetworkType
import io.horizontalsystems.core.BackgroundManager
Expand Down Expand Up @@ -89,10 +90,13 @@ class BitcoinAdapter(
// ignored for now
}

override val unspentOutputs: List<UnspentOutputInfo>
get() = kit.unspentOutputs

override val blockchainType = BlockchainType.Bitcoin

override val usedAddresses: List<UsedAddress>
get() = kit.usedAddresses().map { UsedAddress(it.index, it.address, "https://blockchair.com/bitcoin/address/${it.address}" ) }
override fun usedAddresses(change: Boolean): List<UsedAddress> =
kit.usedAddresses(change).map { UsedAddress(it.index, it.address, "https://blockchair.com/bitcoin/address/${it.address}") }

companion object {
private const val confirmationsThreshold = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ import io.horizontalsystems.bankwallet.modules.transactions.TransactionLockInfo
import io.horizontalsystems.bitcoincore.AbstractKit
import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.core.IPluginData
import io.horizontalsystems.bitcoincore.models.Address
import io.horizontalsystems.bitcoincore.models.TransactionDataSortType
import io.horizontalsystems.bitcoincore.models.TransactionFilterType
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.models.TransactionStatus
import io.horizontalsystems.bitcoincore.models.TransactionType
import io.horizontalsystems.bitcoincore.storage.UnspentOutput
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.core.BackgroundManager
import io.horizontalsystems.hodler.HodlerOutputData
import io.horizontalsystems.hodler.HodlerPlugin
Expand Down Expand Up @@ -210,23 +213,43 @@ abstract class BitcoinBaseAdapter(
}
}

fun send(amount: BigDecimal, address: String, feeRate: Int, pluginData: Map<Byte, IPluginData>?, transactionSorting: TransactionDataSortMode?, logger: AppLogger): Single<Unit> {
fun send(
amount: BigDecimal,
address: String,
feeRate: Int,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?,
transactionSorting: TransactionDataSortMode?,
logger: AppLogger
): Single<Unit> {
val sortingType = getTransactionSortingType(transactionSorting)
return Single.create { emitter ->
try {
logger.info("call btc-kit.send")
kit.send(address, (amount * satoshisInBitcoin).toLong(), true, feeRate, sortingType, pluginData
?: mapOf())
kit.send(
address = address,
value = (amount * satoshisInBitcoin).toLong(),
senderPay = true,
feeRate = feeRate,
sortType = sortingType,
unspentOutputs = unspentOutputs,
pluginData = pluginData ?: mapOf()
)
emitter.onSuccess(Unit)
} catch (ex: Exception) {
emitter.onError(ex)
}
}
}

fun availableBalance(feeRate: Int, address: String?, pluginData: Map<Byte, IPluginData>?): BigDecimal {
fun availableBalance(
feeRate: Int,
address: String?,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?
): BigDecimal {
return try {
val maximumSpendableValue = kit.maximumSpendableValue(address, feeRate, pluginData
val maximumSpendableValue = kit.maximumSpendableValue(address, feeRate, unspentOutputs, pluginData
?: mapOf())
satoshiToBTC(maximumSpendableValue, RoundingMode.CEILING)
} catch (e: Exception) {
Expand All @@ -242,12 +265,30 @@ abstract class BitcoinBaseAdapter(
}
}

fun fee(amount: BigDecimal, feeRate: Int, address: String?, pluginData: Map<Byte, IPluginData>?): BigDecimal? {
fun bitcoinFeeInfo(
amount: BigDecimal,
feeRate: Int,
address: String?,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>?
): BitcoinFeeInfo? {
return try {
val satoshiAmount = (amount * satoshisInBitcoin).toLong()
val fee = kit.fee(satoshiAmount, address, senderPay = true, feeRate = feeRate, pluginData = pluginData
?: mapOf())
satoshiToBTC(fee, RoundingMode.CEILING)
kit.sendInfo(
value = satoshiAmount,
address = address,
senderPay = true,
feeRate = feeRate,
unspentOutputs = unspentOutputs,
pluginData = pluginData ?: mapOf()
).let {
BitcoinFeeInfo(
unspentOutputs = it.unspentOutputs,
fee = satoshiToBTC(it.fee),
changeValue = satoshiToBTC(it.changeValue),
changeAddress = it.changeAddress
)
}
} catch (e: Exception) {
null
}
Expand Down Expand Up @@ -359,3 +400,10 @@ abstract class BitcoinBaseAdapter(
}

}

data class BitcoinFeeInfo(
val unspentOutputs: List<UnspentOutput>,
val fee: BigDecimal,
val changeValue: BigDecimal?,
val changeAddress: Address?
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.core.BackgroundManager
import io.horizontalsystems.marketkit.models.BlockchainType
import io.horizontalsystems.marketkit.models.TokenType
Expand Down Expand Up @@ -84,10 +85,13 @@ class BitcoinCashAdapter(
// ignored for now
}

override val unspentOutputs: List<UnspentOutputInfo>
get() = kit.unspentOutputs

override val blockchainType = BlockchainType.BitcoinCash

override val usedAddresses: List<UsedAddress>
get() = kit.usedAddresses().map { UsedAddress(it.index, it.address, "https://bch.btc.com/bch/address/${it.address}" ) }
override fun usedAddresses(change: Boolean): List<UsedAddress> =
kit.usedAddresses(change).map { UsedAddress(it.index, it.address, "https://bch.btc.com/bch/address/${it.address}") }

companion object {
private const val confirmationsThreshold = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.horizontalsystems.bankwallet.entities.transactionrecords.TransactionRe
import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.core.BackgroundManager
import io.horizontalsystems.dashkit.DashKit
import io.horizontalsystems.dashkit.DashKit.NetworkType
Expand Down Expand Up @@ -77,10 +78,13 @@ class DashAdapter(
// ignored for now
}

override val unspentOutputs: List<UnspentOutputInfo>
get() = kit.unspentOutputs

override val blockchainType = BlockchainType.Dash

override val usedAddresses: List<UsedAddress>
get() = kit.usedAddresses().map { UsedAddress(it.index, it.address, "https://insight.dash.org/insight/address/${it.address}" ) }
override fun usedAddresses(change: Boolean): List<UsedAddress> =
kit.usedAddresses(change).map { UsedAddress(it.index, it.address, "https://insight.dash.org/insight/address/${it.address}") }

companion object {
private const val confirmationsThreshold = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.core.BackgroundManager
import io.horizontalsystems.ecash.ECashKit
import io.horizontalsystems.marketkit.models.BlockchainType
Expand Down Expand Up @@ -79,10 +80,13 @@ class ECashAdapter(
// ignored for now
}

override val unspentOutputs: List<UnspentOutputInfo>
get() = kit.unspentOutputs

override val blockchainType = BlockchainType.ECash

override val usedAddresses: List<UsedAddress>
get() = kit.usedAddresses().map { UsedAddress(it.index, it.address, "https://blockchair.com/ecash/address/${it.address}" ) }
override fun usedAddresses(change: Boolean): List<UsedAddress> =
kit.usedAddresses(change).map { UsedAddress(it.index, it.address, "https://blockchair.com/ecash/address/${it.address}") }

companion object {
private const val confirmationsThreshold = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.horizontalsystems.bitcoincore.BitcoinCore
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo
import io.horizontalsystems.core.BackgroundManager
import io.horizontalsystems.litecoinkit.LitecoinKit
import io.horizontalsystems.litecoinkit.LitecoinKit.NetworkType
Expand Down Expand Up @@ -84,10 +85,13 @@ class LitecoinAdapter(
// ignored for now
}

override val unspentOutputs: List<UnspentOutputInfo>
get() = kit.unspentOutputs

override val blockchainType = BlockchainType.Litecoin

override val usedAddresses: List<UsedAddress>
get() = kit.usedAddresses().map { UsedAddress(it.index, it.address, "https://blockchair.com/litecoin/address/${it.address}" ) }
override fun usedAddresses(change: Boolean): List<UsedAddress> =
kit.usedAddresses(change).map { UsedAddress(it.index, it.address, "https://blockchair.com/litecoin/address/${it.address}") }


companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class LocalStorageManager(
private val APP_AUTO_LOCK_INTERVAL = "app_auto_lock_interval"
private val HIDE_SUSPICIOUS_TX = "hide_suspicious_tx"
private val PIN_RANDOMIZED = "pin_randomized"
private val UTXO_EXPERT_MODE = "utxo_expert_mode"

private val _utxoExpertModeEnabledFlow = MutableStateFlow(false)
override val utxoExpertModeEnabledFlow = _utxoExpertModeEnabledFlow

private val gson by lazy { Gson() }

Expand Down Expand Up @@ -499,6 +503,15 @@ class LocalStorageManager(
preferences.edit().putString(APP_AUTO_LOCK_INTERVAL, value.raw).apply()
}

override var utxoExpertModeEnabled: Boolean
get() = preferences.getBoolean(UTXO_EXPERT_MODE, false)
set(value) {
preferences.edit().putBoolean(UTXO_EXPERT_MODE, value).apply()
_utxoExpertModeEnabledFlow.update {
value
}
}

private fun getSwapProviderKey(blockchainType: BlockchainType): String {
return SWAP_PROVIDER + blockchainType.uid
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class DepositAddressViewModel(
viewState = viewState,
address = address,
usedAddresses = listOf(),
usedChangeAddresses = listOf(),
uri = uri,
networkName = networkName,
watchAccount = watchAccount,
Expand Down Expand Up @@ -80,6 +81,7 @@ class DepositAddressViewModel(
viewState = viewState,
address = address,
usedAddresses = listOf(),
usedChangeAddresses = listOf(),
uri = uri,
networkName = networkName,
watchAccount = watchAccount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ fun CexDepositScreen(
type = "text/plain"
})
},
showUsedAddresses = { usedAddresses ->
viewModel.usedAddressesParams = UsedAddressesParams(cexAsset.name, usedAddresses)
showUsedAddresses = { usedAddresses, usedChangeAddresses ->
viewModel.usedAddressesParams = UsedAddressesParams(cexAsset.name, usedAddresses, usedChangeAddresses)
navController.navigate(USED_ADDRESS_SCREEN)
},
onBackPress = navigateBack(fragmentNavController, navController),
Expand Down
Loading