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

Add ability to send selected UTXOs #636

Merged
merged 1 commit into from
Dec 21, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ class BitcoinCore(
return transactionFeeCalculator?.fee(value, feeRate, senderPay, address, pluginData) ?: throw CoreError.ReadOnlyCore
}

fun fee(
unspentOutputs: List<UnspentOutput>,
address: String? = null,
feeRate: Int,
pluginData: Map<Byte, IPluginData>
): Long {
return transactionFeeCalculator?.fee(
unspentOutputs,
feeRate,
address,
pluginData
) ?: throw CoreError.ReadOnlyCore
}

fun getSpendableUtxo() = dataProvider.getSpendableUtxo()

fun send(
address: String,
unspentOutputs: List<UnspentOutput>,
feeRate: Int,
sortType: TransactionDataSortType,
pluginData: Map<Byte, IPluginData>
): FullTransaction {
return transactionCreator?.create(address, unspentOutputs, feeRate, sortType, pluginData) ?: throw CoreError.ReadOnlyCore
}

fun send(
address: String,
value: Long,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,14 @@ class BitcoinCoreBuilder {
val lockTimeSetter = LockTimeSetter(storage)
val signer = TransactionSigner(ecdsaInputSigner, schnorrInputSigner)
val transactionBuilder = TransactionBuilder(recipientSetter, outputSetter, inputSetter, signer, lockTimeSetter)
transactionFeeCalculator = TransactionFeeCalculator(recipientSetter, inputSetter, addressConverter, publicKeyManager, purpose.scriptType)
transactionFeeCalculator = TransactionFeeCalculator(
recipientSetter,
inputSetter,
addressConverter,
publicKeyManager,
purpose.scriptType,
transactionSizeCalculatorInstance
)
val transactionSendTimer = TransactionSendTimer(60)
val transactionSenderInstance = TransactionSender(
pendingTransactionSyncer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import io.horizontalsystems.bitcoincore.blocks.IBlockchainDataListener
import io.horizontalsystems.bitcoincore.extensions.hexToByteArray
import io.horizontalsystems.bitcoincore.extensions.toReversedHex
import io.horizontalsystems.bitcoincore.managers.UnspentOutputProvider
import io.horizontalsystems.bitcoincore.models.*
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.Block
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.Transaction
import io.horizontalsystems.bitcoincore.models.TransactionFilterType
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.storage.TransactionWithBlock
import io.reactivex.Single
import io.reactivex.disposables.Disposable
Expand Down Expand Up @@ -99,6 +104,8 @@ class DataProvider(
}
}

fun getSpendableUtxo() = unspentOutputProvider.getSpendableUtxo()

private fun blockInfo(block: Block) = BlockInfo(
block.headerHash.toReversedHex(),
block.height,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ class TransactionCreator(
}
}

@Throws
fun create(
address: String,
unspentOutputs: List<UnspentOutput>,
feeRate: Int,
sortType: TransactionDataSortType,
pluginData: Map<Byte, IPluginData>,
): FullTransaction {
return create {
builder.buildTransaction(unspentOutputs, address, feeRate, sortType, pluginData)
}
}

@Throws
fun create(unspentOutput: UnspentOutput, toAddress: String, feeRate: Int, sortType: TransactionDataSortType): FullTransaction {
return create {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.horizontalsystems.bitcoincore.core.IPluginData
import io.horizontalsystems.bitcoincore.core.IPublicKeyManager
import io.horizontalsystems.bitcoincore.core.IRecipientSetter
import io.horizontalsystems.bitcoincore.models.TransactionDataSortType
import io.horizontalsystems.bitcoincore.storage.UnspentOutput
import io.horizontalsystems.bitcoincore.transactions.builder.InputSetter
import io.horizontalsystems.bitcoincore.transactions.builder.MutableTransaction
import io.horizontalsystems.bitcoincore.transactions.scripts.ScriptType
Expand All @@ -14,7 +15,8 @@ class TransactionFeeCalculator(
private val inputSetter: InputSetter,
private val addressConverter: AddressConverterChain,
private val publicKeyManager: IPublicKeyManager,
private val changeScriptType: ScriptType
private val changeScriptType: ScriptType,
private val transactionSizeCalculator: TransactionSizeCalculator
) {

fun fee(value: Long, feeRate: Int, senderPay: Boolean, toAddress: String?, pluginData: Map<Byte, IPluginData>): Long {
Expand All @@ -29,6 +31,23 @@ class TransactionFeeCalculator(
return inputsTotalValue - outputsTotalValue
}

fun fee(
unspentOutputs: List<UnspentOutput>,
feeRate: Int,
toAddress: String?,
pluginData: Map<Byte, IPluginData>
): Long {
val mutableTransaction = MutableTransaction()

val value = unspentOutputs.sumOf { it.output.value }

recipientSetter.setRecipient(mutableTransaction, toAddress ?: sampleAddress(), value, pluginData, true)
val transactionSize =
transactionSizeCalculator.transactionSize(unspentOutputs.map { it.output }, listOf(mutableTransaction.recipientAddress.scriptType), mutableTransaction.getPluginDataOutputSize())

return transactionSize * feeRate
}

private fun sampleAddress(): String {
return addressConverter.convert(publicKey = publicKeyManager.changePublicKey(), scriptType = changeScriptType).stringValue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,26 @@ class InputSetter(
throw TransactionBuilder.BuilderException.NotSupportedScriptType()
}

setInputs(mutableTransaction, listOf(unspentOutput), feeRate)
}

fun setInputs(mutableTransaction: MutableTransaction, unspentOutputs: List<UnspentOutput>, feeRate: Int) {
// Calculate fee
val transactionSize =
transactionSizeCalculator.transactionSize(listOf(unspentOutput.output), listOf(mutableTransaction.recipientAddress.scriptType), 0)
transactionSizeCalculator.transactionSize(unspentOutputs.map { it.output }, listOf(mutableTransaction.recipientAddress.scriptType), mutableTransaction.getPluginDataOutputSize())

val fee = transactionSize * feeRate

if (unspentOutput.output.value < fee) {
val value = unspentOutputs.sumOf { it.output.value }
if (value < fee) {
throw TransactionBuilder.BuilderException.FeeMoreThanValue()
}

// Add to mutable transaction
mutableTransaction.addInput(inputToSign(unspentOutput))
mutableTransaction.recipientValue = unspentOutput.output.value - fee
unspentOutputs.forEach {unspentOutput ->
mutableTransaction.addInput(inputToSign(unspentOutput))
}
mutableTransaction.recipientValue = value - fee
}

private fun inputToSign(unspentOutput: UnspentOutput): InputToSign {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ class TransactionBuilder(
return mutableTransaction.build()
}

fun buildTransaction(unspentOutputs: List<UnspentOutput>, toAddress: String, feeRate: Int, sortType: TransactionDataSortType, pluginData: Map<Byte, IPluginData>): FullTransaction {
val mutableTransaction = MutableTransaction(false)

val value = unspentOutputs.sumOf { it.output.value }
recipientSetter.setRecipient(mutableTransaction, toAddress, value, pluginData, false)
inputSetter.setInputs(mutableTransaction, unspentOutputs, feeRate)
lockTimeSetter.setLockTime(mutableTransaction)

outputSetter.setOutputs(mutableTransaction, sortType)
signer.sign(mutableTransaction)

return mutableTransaction.build()
}

open class BuilderException : Exception() {
class FeeMoreThanValue : BuilderException()
class NotSupportedScriptType : BuilderException()
Expand Down
Loading