Skip to content

Commit

Permalink
Add memo support
Browse files Browse the repository at this point in the history
  • Loading branch information
abdrasulov committed Apr 11, 2024
1 parent 8acc12f commit 1c802e2
Show file tree
Hide file tree
Showing 21 changed files with 187 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import io.horizontalsystems.bitcoincore.BitcoinCore.KitState
import io.horizontalsystems.bitcoincore.core.IPluginData
import io.horizontalsystems.bitcoincore.exceptions.AddressFormatException
import io.horizontalsystems.bitcoincore.managers.SendValueErrors
import io.horizontalsystems.bitcoincore.models.*
import io.horizontalsystems.bitcoincore.models.BalanceInfo
import io.horizontalsystems.bitcoincore.models.BitcoinSendInfo
import io.horizontalsystems.bitcoincore.models.BlockInfo
import io.horizontalsystems.bitcoincore.models.TransactionDataSortType
import io.horizontalsystems.bitcoincore.models.TransactionFilterType
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoinkit.BitcoinKit
import io.horizontalsystems.hdwalletkit.HDWallet.Purpose
import io.horizontalsystems.hodler.HodlerData
Expand Down Expand Up @@ -156,6 +161,7 @@ class MainViewModel : ViewModel(), BitcoinKit.Listener {
try {
val transaction = bitcoinKit.send(
address!!,
null,
amount!!,
feeRate = feePriority.feeRate,
sortType = TransactionDataSortType.Shuffle,
Expand All @@ -182,7 +188,7 @@ class MainViewModel : ViewModel(), BitcoinKit.Listener {

fun onMaxClick() {
try {
amountLiveData.value = bitcoinKit.maximumSpendableValue(address, feePriority.feeRate, null, getPluginData())
amountLiveData.value = bitcoinKit.maximumSpendableValue(address, null, feePriority.feeRate, null, getPluginData())
} catch (e: Exception) {
amountLiveData.value = 0
errorLiveData.value = when (e) {
Expand All @@ -206,7 +212,7 @@ class MainViewModel : ViewModel(), BitcoinKit.Listener {
}

private fun fee(value: Long, address: String? = null): BitcoinSendInfo {
return bitcoinKit.sendInfo(value, address, feeRate = feePriority.feeRate, unspentOutputs = null, pluginData = getPluginData())
return bitcoinKit.sendInfo(value, address, null, feeRate = feePriority.feeRate, unspentOutputs = null, pluginData = getPluginData())
}

private fun getPluginData(): MutableMap<Byte, IPluginData> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import io.horizontalsystems.dashkit.models.DashTransactionInfo
import io.horizontalsystems.hodler.HodlerOutputData
import io.horizontalsystems.hodler.HodlerPlugin
import java.text.DateFormat
import java.util.*
import java.util.Date
import java.util.Locale

class TransactionsFragment : Fragment(), ViewHolderTransaction.Listener {

Expand Down Expand Up @@ -165,6 +166,7 @@ class ViewHolderTransaction(val containerView: View, private val listener: Liste
sb.append("\n value: ${it.value}")
sb.append("\n mine: ${it.mine}")
sb.append("\n change: ${it.changeOutput}")
sb.append("\n memo: ${it.memo}")

if (it.pluginId == HodlerPlugin.id && it.pluginData != null) {
(it.pluginData as? HodlerOutputData)?.let { hodlerData ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ abstract class AbstractKit {
fun sendInfo(
value: Long,
address: String? = null,
memo: String?,
senderPay: Boolean = true,
feeRate: Int,
unspentOutputs: List<UnspentOutputInfo>?,
Expand All @@ -79,6 +80,7 @@ abstract class AbstractKit {
return bitcoinCore.sendInfo(
value = value,
address = address,
memo = memo,
senderPay = senderPay,
feeRate = feeRate,
unspentOutputs = unspentOutputs,
Expand All @@ -88,6 +90,7 @@ abstract class AbstractKit {

fun send(
address: String,
memo: String?,
value: Long,
senderPay: Boolean = true,
feeRate: Int,
Expand All @@ -96,23 +99,25 @@ abstract class AbstractKit {
pluginData: Map<Byte, IPluginData> = mapOf(),
rbfEnabled: Boolean,
): FullTransaction {
return bitcoinCore.send(address, value, senderPay, feeRate, sortType, unspentOutputs, pluginData, rbfEnabled)
return bitcoinCore.send(address, memo, value, senderPay, feeRate, sortType, unspentOutputs, pluginData, rbfEnabled)
}

fun send(
address: String,
memo: String?,
value: Long,
senderPay: Boolean = true,
feeRate: Int,
sortType: TransactionDataSortType,
pluginData: Map<Byte, IPluginData> = mapOf(),
rbfEnabled: Boolean,
): FullTransaction {
return bitcoinCore.send(address, value, senderPay, feeRate, sortType, null, pluginData, rbfEnabled)
return bitcoinCore.send(address, memo, value, senderPay, feeRate, sortType, null, pluginData, rbfEnabled)
}

fun send(
hash: ByteArray,
memo: String?,
scriptType: ScriptType,
value: Long,
senderPay: Boolean = true,
Expand All @@ -121,23 +126,24 @@ abstract class AbstractKit {
unspentOutputs: List<UnspentOutputInfo>? = null,
rbfEnabled: Boolean,
): FullTransaction {
return bitcoinCore.send(hash, scriptType, value, senderPay, feeRate, sortType, unspentOutputs, rbfEnabled)
return bitcoinCore.send(hash, memo, scriptType, value, senderPay, feeRate, sortType, unspentOutputs, rbfEnabled)
}

fun send(
hash: ByteArray,
memo: String?,
scriptType: ScriptType,
value: Long,
senderPay: Boolean = true,
feeRate: Int,
sortType: TransactionDataSortType,
rbfEnabled: Boolean,
): FullTransaction {
return bitcoinCore.send(hash, scriptType, value, senderPay, feeRate, sortType, null, rbfEnabled)
return bitcoinCore.send(hash, memo, scriptType, value, senderPay, feeRate, sortType, null, rbfEnabled)
}

fun redeem(unspentOutput: UnspentOutput, address: String, feeRate: Int, sortType: TransactionDataSortType, rbfEnabled: Boolean): FullTransaction {
return bitcoinCore.redeem(unspentOutput, address, feeRate, sortType, rbfEnabled)
fun redeem(unspentOutput: UnspentOutput, address: String, memo: String?, feeRate: Int, sortType: TransactionDataSortType, rbfEnabled: Boolean): FullTransaction {
return bitcoinCore.redeem(unspentOutput, address, memo, feeRate, sortType, rbfEnabled)
}

fun receiveAddress(): String {
Expand Down Expand Up @@ -180,8 +186,8 @@ abstract class AbstractKit {
bitcoinCore.watchTransaction(filter, listener)
}

fun maximumSpendableValue(address: String?, feeRate: Int, unspentOutputs: List<UnspentOutputInfo>?, pluginData: Map<Byte, IPluginData>): Long {
return bitcoinCore.maximumSpendableValue(address, feeRate, unspentOutputs, pluginData)
fun maximumSpendableValue(address: String?, memo: String?, feeRate: Int, unspentOutputs: List<UnspentOutputInfo>?, pluginData: Map<Byte, IPluginData>): Long {
return bitcoinCore.maximumSpendableValue(address, memo, feeRate, unspentOutputs, pluginData)
}

fun minimumSpendableValue(address: String?): Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class BitcoinCore(
fun sendInfo(
value: Long,
address: String? = null,
memo: String?,
senderPay: Boolean = true,
feeRate: Int,
unspentOutputs: List<UnspentOutputInfo>?,
Expand All @@ -202,13 +203,15 @@ class BitcoinCore(
feeRate = feeRate,
senderPay = senderPay,
toAddress = address,
memo = memo,
unspentOutputs = outputs,
pluginData = pluginData
) ?: throw CoreError.ReadOnlyCore
}

fun send(
address: String,
memo: String?,
value: Long,
senderPay: Boolean = true,
feeRate: Int,
Expand All @@ -224,6 +227,7 @@ class BitcoinCore(
}
return transactionCreator?.create(
toAddress = address,
memo = memo,
value = value,
feeRate = feeRate,
senderPay = senderPay,
Expand All @@ -236,6 +240,7 @@ class BitcoinCore(

fun send(
hash: ByteArray,
memo: String?,
scriptType: ScriptType,
value: Long,
senderPay: Boolean = true,
Expand All @@ -252,6 +257,7 @@ class BitcoinCore(
}
return transactionCreator?.create(
toAddress = address.stringValue,
memo = memo,
value = value,
feeRate = feeRate,
senderPay = senderPay,
Expand All @@ -262,8 +268,8 @@ class BitcoinCore(
) ?: throw CoreError.ReadOnlyCore
}

fun redeem(unspentOutput: UnspentOutput, address: String, feeRate: Int, sortType: TransactionDataSortType, rbfEnabled: Boolean): FullTransaction {
return transactionCreator?.create(unspentOutput, address, feeRate, sortType, rbfEnabled) ?: throw CoreError.ReadOnlyCore
fun redeem(unspentOutput: UnspentOutput, address: String, memo: String?, feeRate: Int, sortType: TransactionDataSortType, rbfEnabled: Boolean): FullTransaction {
return transactionCreator?.create(unspentOutput, address, memo, feeRate, sortType, rbfEnabled) ?: throw CoreError.ReadOnlyCore
}

fun receiveAddress(): String {
Expand Down Expand Up @@ -406,6 +412,7 @@ class BitcoinCore(

fun maximumSpendableValue(
address: String?,
memo: String?,
feeRate: Int,
unspentOutputs: List<UnspentOutputInfo>?,
pluginData: Map<Byte, IPluginData>
Expand All @@ -425,6 +432,7 @@ class BitcoinCore(
feeRate = feeRate,
senderPay = false,
toAddress = address,
memo = memo,
unspentOutputs = outputs,
pluginData = pluginData
).fee
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package io.horizontalsystems.bitcoincore.core

import io.horizontalsystems.bitcoincore.extensions.toReversedHex
import io.horizontalsystems.bitcoincore.models.*
import io.horizontalsystems.bitcoincore.io.BitcoinInput
import io.horizontalsystems.bitcoincore.models.InvalidTransaction
import io.horizontalsystems.bitcoincore.models.Transaction
import io.horizontalsystems.bitcoincore.models.TransactionInfo
import io.horizontalsystems.bitcoincore.models.TransactionInputInfo
import io.horizontalsystems.bitcoincore.models.TransactionMetadata
import io.horizontalsystems.bitcoincore.models.TransactionOutput
import io.horizontalsystems.bitcoincore.models.TransactionOutputInfo
import io.horizontalsystems.bitcoincore.models.TransactionStatus
import io.horizontalsystems.bitcoincore.models.rbfEnabled
import io.horizontalsystems.bitcoincore.storage.FullTransactionInfo
import io.horizontalsystems.bitcoincore.transactions.scripts.ScriptType
import java.io.ByteArrayInputStream

class BaseTransactionInfoConverter(private val pluginManager: PluginManager) {

Expand Down Expand Up @@ -37,6 +48,7 @@ class BaseTransactionInfoConverter(private val pluginManager: PluginManager) {
changeOutput = output.changeOutput,
value = output.value,
address = output.address,
memo = parseMemo(output),
pluginId = output.pluginId,
pluginDataString = output.pluginData,
pluginData = pluginManager.parsePluginData(output, transaction.timestamp))
Expand All @@ -63,6 +75,16 @@ class BaseTransactionInfoConverter(private val pluginManager: PluginManager) {
)
}

private fun parseMemo(output: TransactionOutput): String? {
if (output.scriptType != ScriptType.NULL_DATA) return null
val payload = output.lockingScriptPayload ?: return null
if (payload.isEmpty()) return null

val input = BitcoinInput(ByteArrayInputStream(payload))
input.readByte() // op_return
return input.readString()
}

private fun getInvalidTransactionInfo(
transaction: InvalidTransaction,
metadata: TransactionMetadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ interface IRecipientSetter {
toAddress: String,
value: Long,
pluginData: Map<Byte, IPluginData>,
skipChecking: Boolean
skipChecking: Boolean,
memo: String?
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import io.horizontalsystems.bitcoincore.storage.UnspentOutput
import io.horizontalsystems.bitcoincore.transactions.scripts.ScriptType

interface IUnspentOutputSelector {
fun select(value: Long, feeRate: Int, outputScriptType: ScriptType = ScriptType.P2PKH, changeType: ScriptType = ScriptType.P2PKH, senderPay: Boolean, pluginDataOutputSize: Int): SelectedUnspentOutputInfo
fun select(
value: Long,
memo: String?,
feeRate: Int,
outputScriptType: ScriptType = ScriptType.P2PKH,
changeType: ScriptType = ScriptType.P2PKH,
senderPay: Boolean,
pluginDataOutputSize: Int
): SelectedUnspentOutputInfo
}

data class SelectedUnspentOutputInfo(
Expand All @@ -27,12 +35,28 @@ class UnspentOutputSelectorChain(private val unspentOutputProvider: IUnspentOutp
val all: List<UnspentOutput>
get() = unspentOutputProvider.getSpendableUtxo()

override fun select(value: Long, feeRate: Int, outputScriptType: ScriptType, changeType: ScriptType, senderPay: Boolean, pluginDataOutputSize: Int): SelectedUnspentOutputInfo {
override fun select(
value: Long,
memo: String?,
feeRate: Int,
outputScriptType: ScriptType,
changeType: ScriptType,
senderPay: Boolean,
pluginDataOutputSize: Int
): SelectedUnspentOutputInfo {
var lastError: SendValueErrors? = null

for (selector in concreteSelectors) {
try {
return selector.select(value, feeRate, outputScriptType, changeType, senderPay, pluginDataOutputSize)
return selector.select(
value,
memo,
feeRate,
outputScriptType,
changeType,
senderPay,
pluginDataOutputSize
)
} catch (e: SendValueErrors) {
lastError = e
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class UnspentOutputQueue(
sizeCalculator.transactionSize(
previousOutputs = selectedOutputs.map { it.output },
outputs = listOf(parameters.outputScriptType),
memo = parameters.memo,
pluginDataOutputSize = parameters.pluginDataOutputSize
) * parameters.fee

Expand All @@ -83,6 +84,7 @@ class UnspentOutputQueue(
data class Parameters(
val value: Long,
val senderPay: Boolean,
val memo: String?,
val fee: Int,
val outputsLimit: Int?,
val outputScriptType: ScriptType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class UnspentOutputSelector(
@Throws(SendValueErrors::class)
override fun select(
value: Long,
memo: String?,
feeRate: Int,
outputScriptType: ScriptType,
changeType: ScriptType,
Expand All @@ -39,6 +40,7 @@ class UnspentOutputSelector(
val params = UnspentOutputQueue.Parameters(
value = value,
senderPay = senderPay,
memo = memo,
fee = feeRate,
outputsLimit = outputsLimit,
outputScriptType = outputScriptType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class UnspentOutputSelectorSingleNoChange(

override fun select(
value: Long,
memo: String?,
feeRate: Int,
outputScriptType: ScriptType,
changeType: ScriptType,
Expand Down Expand Up @@ -42,6 +43,7 @@ class UnspentOutputSelectorSingleNoChange(
val params = UnspentOutputQueue.Parameters(
value = value,
senderPay = senderPay,
memo = memo,
fee = feeRate,
outputsLimit = null,
outputScriptType = outputScriptType,
Expand Down
Loading

0 comments on commit 1c802e2

Please sign in to comment.