diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCore.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCore.kt index 3317ac7ba..907fea70a 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCore.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCore.kt @@ -173,6 +173,32 @@ class BitcoinCore( return transactionFeeCalculator?.fee(value, feeRate, senderPay, address, pluginData) ?: throw CoreError.ReadOnlyCore } + fun fee( + unspentOutputs: List, + address: String? = null, + feeRate: Int, + pluginData: Map + ): Long { + return transactionFeeCalculator?.fee( + unspentOutputs, + feeRate, + address, + pluginData + ) ?: throw CoreError.ReadOnlyCore + } + + fun getSpendableUtxo() = dataProvider.getSpendableUtxo() + + fun send( + address: String, + unspentOutputs: List, + feeRate: Int, + sortType: TransactionDataSortType, + pluginData: Map + ): FullTransaction { + return transactionCreator?.create(address, unspentOutputs, feeRate, sortType, pluginData) ?: throw CoreError.ReadOnlyCore + } + fun send( address: String, value: Long, diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCoreBuilder.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCoreBuilder.kt index 39c47f54c..d7216deb0 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCoreBuilder.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/BitcoinCoreBuilder.kt @@ -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, diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/core/DataProvider.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/core/DataProvider.kt index 34bae095f..5699b443d 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/core/DataProvider.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/core/DataProvider.kt @@ -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 @@ -99,6 +104,8 @@ class DataProvider( } } + fun getSpendableUtxo() = unspentOutputProvider.getSpendableUtxo() + private fun blockInfo(block: Block) = BlockInfo( block.headerHash.toReversedHex(), block.height, diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionCreator.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionCreator.kt index 06491517a..ef018f7fa 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionCreator.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionCreator.kt @@ -20,6 +20,19 @@ class TransactionCreator( } } + @Throws + fun create( + address: String, + unspentOutputs: List, + feeRate: Int, + sortType: TransactionDataSortType, + pluginData: Map, + ): FullTransaction { + return create { + builder.buildTransaction(unspentOutputs, address, feeRate, sortType, pluginData) + } + } + @Throws fun create(unspentOutput: UnspentOutput, toAddress: String, feeRate: Int, sortType: TransactionDataSortType): FullTransaction { return create { diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionFeeCalculator.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionFeeCalculator.kt index 4e7244c6c..ecccb1c62 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionFeeCalculator.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/TransactionFeeCalculator.kt @@ -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 @@ -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): Long { @@ -29,6 +31,23 @@ class TransactionFeeCalculator( return inputsTotalValue - outputsTotalValue } + fun fee( + unspentOutputs: List, + feeRate: Int, + toAddress: String?, + pluginData: Map + ): 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 } diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/InputSetter.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/InputSetter.kt index 2bf0490cd..c5c008c1d 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/InputSetter.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/InputSetter.kt @@ -61,18 +61,26 @@ class InputSetter( throw TransactionBuilder.BuilderException.NotSupportedScriptType() } + setInputs(mutableTransaction, listOf(unspentOutput), feeRate) + } + + fun setInputs(mutableTransaction: MutableTransaction, unspentOutputs: List, 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 { diff --git a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/TransactionBuilder.kt b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/TransactionBuilder.kt index 8547f74fe..2534b2d15 100644 --- a/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/TransactionBuilder.kt +++ b/bitcoincore/src/main/kotlin/io/horizontalsystems/bitcoincore/transactions/builder/TransactionBuilder.kt @@ -40,6 +40,20 @@ class TransactionBuilder( return mutableTransaction.build() } + fun buildTransaction(unspentOutputs: List, toAddress: String, feeRate: Int, sortType: TransactionDataSortType, pluginData: Map): 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()