Skip to content

Commit

Permalink
AND-8877 Fix bugs and review issues
Browse files Browse the repository at this point in the history
  • Loading branch information
nzeeei authored and kozarezvlad committed Dec 6, 2024
1 parent eafb815 commit 4908301
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ internal class HederaWalletManager(
}
}

override suspend fun discardRequirements(currencyType: CryptoCurrencyType): SimpleResult {
return SimpleResult.Success
}

override suspend fun send(
transactionData: TransactionData,
signer: TransactionSigner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import java.math.BigInteger
class KaspaTransactionBuilder(
private val publicKey: Wallet.PublicKey,
) {
private lateinit var transaction: KaspaTransaction
private var networkParameters = KaspaMainNetParams()
var unspentOutputs: List<KaspaUnspentOutput>? = null

Expand All @@ -34,7 +33,7 @@ class KaspaTransactionBuilder(
private val envelopeAdapter by lazy { moshi.adapter<Envelope>() }

@Suppress("MagicNumber")
fun buildToSign(transactionData: TransactionData): Result<List<ByteArray>> {
fun buildToSign(transactionData: TransactionData): Result<KaspaTransaction> {
transactionData.requireUncompiled()

if (unspentOutputs.isNullOrEmpty()) {
Expand Down Expand Up @@ -74,7 +73,7 @@ class KaspaTransactionBuilder(
null -> error("Null script type") // should never happen
}

transaction = createKaspaTransaction(
val transaction = createKaspaTransaction(
networkParameters = networkParameters,
unspentOutputs = unspentsToSpend,
transformer = { kaspaTransaction ->
Expand All @@ -92,18 +91,18 @@ class KaspaTransactionBuilder(
},
)

return Result.Success(getHashesForSign(transaction))
return Result.Success(transaction)
}

fun buildToSend(signatures: ByteArray, transaction: KaspaTransaction = this.transaction): KaspaTransactionBody {
fun buildToSend(signatures: ByteArray, transaction: KaspaTransaction): KaspaTransactionBody {
for (index in transaction.inputs.indices) {
val signature = extractSignature(index, signatures)
transaction.inputs[index].scriptSig = ScriptBuilder().data(signature).build()
}
return buildForSendInternal(transaction)
}

fun buildKRC20RevealToSend(
internal fun buildKRC20RevealToSend(
signatures: ByteArray,
redeemScript: RedeemScript,
transaction: KaspaTransaction,
Expand All @@ -123,15 +122,21 @@ class KaspaTransactionBuilder(
}

@Suppress("LongMethod", "MagicNumber")
fun buildKRC20CommitTransactionToSign(
internal fun buildKRC20CommitTransactionToSign(
transactionData: TransactionData,
dust: BigDecimal,
dust: BigDecimal?,
includeFee: Boolean = true,
): Result<CommitTransaction> {
transactionData.requireUncompiled()

require(transactionData.amount.type is AmountType.Token)

if (unspentOutputs.isNullOrEmpty()) {
return Result.Failure(
BlockchainSdkError.CustomError("Unspent outputs are missing"),
)
}

val unspentsToSpend = getUnspentsToSpend()

val transactionFeeAmountValue = transactionData.fee?.amount?.value ?: BigDecimal.ZERO
Expand All @@ -151,7 +156,7 @@ class KaspaTransactionBuilder(
val revealFeeParams = (transactionData.fee as? Fee.Kaspa)?.revealTransactionFee?.takeIf { includeFee }
// if we don't know the commission, commission for reveal transaction will be set to zero
val feeEstimationRevealTransactionValue = revealFeeParams?.value ?: BigDecimal.ZERO
val targetOutputAmountValue = feeEstimationRevealTransactionValue + dust
val targetOutputAmountValue = feeEstimationRevealTransactionValue + (dust ?: BigDecimal.ZERO)

val resultChange = calculateChange(
amount = targetOutputAmountValue,
Expand All @@ -173,7 +178,7 @@ class KaspaTransactionBuilder(
envelope = envelope,
)

transaction = createKaspaTransaction(
val transaction = createKaspaTransaction(
networkParameters = networkParameters,
unspentOutputs = unspentsToSpend,
transformer = { kaspaTransaction ->
Expand All @@ -197,7 +202,7 @@ class KaspaTransactionBuilder(
)
val commitTransaction = CommitTransaction(
transaction = transaction,
hashes = getHashesForSign(),
hashes = getHashesForSign(transaction),
redeemScript = redeemScript,
sourceAddress = transactionData.sourceAddress,
params = IncompleteTokenTransactionParams(
Expand Down Expand Up @@ -301,7 +306,7 @@ class KaspaTransactionBuilder(

fun getUnspentsToSpend() = unspentOutputs!!.sortedByDescending { it.amount }.take(getUnspentsToSpendCount())

private fun getHashesForSign(transaction: KaspaTransaction = this.transaction): List<ByteArray> {
fun getHashesForSign(transaction: KaspaTransaction): List<ByteArray> {
val hashesForSign: MutableList<ByteArray> = MutableList(transaction.inputs.size) { byteArrayOf() }
for (input in transaction.inputs) {
val index = input.index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,28 @@ internal class KaspaWalletManager(

val coinBalance = coinBalanceDeferred.await()

if (coinBalance is Result.Success && tokensBalances is Result.Success) {
updateWallet(coinBalance.data, tokensBalances.data)
} else if (coinBalance is Result.Failure) {
updateError(coinBalance.error)
} else if (tokensBalances is Result.Failure) {
updateError(tokensBalances.error)
if (tokensBalances is Result.Success) {
updateWalletTokens(tokensBalances.data)
}

when (coinBalance) {
is Result.Success -> updateWallet(coinBalance.data)
is Result.Failure -> updateError(coinBalance.error)
}
}
}

private fun updateWallet(response: KaspaInfoResponse, tokensInfo: List<KaspaKRC20InfoResponse>) {
private fun updateWallet(response: KaspaInfoResponse) {
Log.d(this::class.java.simpleName, "Balance is ${response.balance}")
if (response.balance != wallet.amounts[AmountType.Coin]?.value) {
// assume outgoing transaction has been finalized if balance has changed
wallet.recentTransactions.clear()
}
wallet.changeAmountValue(AmountType.Coin, response.balance)
transactionBuilder.unspentOutputs = response.unspentOutputs
}

private fun updateWalletTokens(tokensInfo: List<KaspaKRC20InfoResponse>) {
tokensInfo.forEach { result ->
val token = result.token
val balance = result.balance
Expand Down Expand Up @@ -133,7 +136,7 @@ internal class KaspaWalletManager(
).let {
when (it) {
is Result.Failure -> it
is Result.Success -> Result.Success(it.data.hashes)
is Result.Success -> Result.Success(it.data.transaction)
}
}
else -> error("unknown amount type for fee estimation")
Expand All @@ -142,10 +145,13 @@ internal class KaspaWalletManager(
when (buildTransactionResult) {
is Result.Failure -> return buildTransactionResult
is Result.Success -> {
return when (val signerResult = dummySigner.sign(buildTransactionResult.data, wallet.publicKey)) {
val transaction = buildTransactionResult.data
val hashesToSign = transactionBuilder.getHashesForSign(transaction)
return when (val signerResult = dummySigner.sign(hashesToSign, wallet.publicKey)) {
is CompletionResult.Success -> {
val transactionToSend = transactionBuilder.buildToSend(
signerResult.data.reduce { acc, bytes -> acc + bytes },
signatures = signerResult.data.reduce { acc, bytes -> acc + bytes },
transaction = transaction,
)
when (val sendResult = networkProvider.calculateFee(transactionToSend.transaction)) {
is Result.Failure -> sendResult
Expand Down Expand Up @@ -228,7 +234,7 @@ internal class KaspaWalletManager(
transactionData = incompleteTokenTransaction
.toIncompleteTokenTransactionParams()
.toTransactionData(
token = currencyType.info,
type = AmountType.Token(currencyType.info),
),
signer = signer,
)
Expand All @@ -241,13 +247,14 @@ internal class KaspaWalletManager(
}
}

override suspend fun discardRequirements(currencyType: CryptoCurrencyType) {
override suspend fun discardRequirements(currencyType: CryptoCurrencyType): SimpleResult {
when (currencyType) {
is CryptoCurrencyType.Coin -> return
is CryptoCurrencyType.Coin -> Unit
is CryptoCurrencyType.Token -> {
removeIncompleteTokenTransaction(currencyType.info)
}
}
return SimpleResult.Success
}

private suspend fun sendCoinTransaction(
Expand All @@ -257,10 +264,13 @@ internal class KaspaWalletManager(
when (val buildTransactionResult = transactionBuilder.buildToSign(transactionData)) {
is Result.Failure -> return buildTransactionResult
is Result.Success -> {
return when (val signerResult = signer.sign(buildTransactionResult.data, wallet.publicKey)) {
val transaction = buildTransactionResult.data
val hashesToSign = transactionBuilder.getHashesForSign(transaction)
return when (val signerResult = signer.sign(hashesToSign, wallet.publicKey)) {
is CompletionResult.Success -> {
val transactionToSend = transactionBuilder.buildToSend(
signerResult.data.reduce { acc, bytes -> acc + bytes },
signatures = signerResult.data.reduce { acc, bytes -> acc + bytes },
transaction = transaction,
)
when (val sendResult = networkProvider.sendTransaction(transactionToSend)) {
is Result.Failure -> sendResult
Expand Down Expand Up @@ -322,7 +332,7 @@ internal class KaspaWalletManager(
) {
is Result.Failure -> sendCommitResult
is Result.Success -> {
store(
storeIncompleteTokenTransaction(
token = token,
data = commitTransaction.data
.params
Expand All @@ -339,6 +349,7 @@ internal class KaspaWalletManager(
is Result.Success -> {
val hash = sendRevealResult.data
transactionData.hash = hash
wallet.addOutgoingTransaction(transactionData)
removeIncompleteTokenTransaction(token)
Result.Success(TransactionSendResult(hash ?: ""))
}
Expand All @@ -356,7 +367,8 @@ internal class KaspaWalletManager(
}
}

private fun IncompleteTokenTransactionParams.toTransactionData(token: Token): TransactionData.Uncompiled {
private fun IncompleteTokenTransactionParams.toTransactionData(type: AmountType.Token): TransactionData.Uncompiled {
val token = type.token
val tokenValue = BigDecimal(envelope.amt)

val transactionAmount = tokenValue.movePointLeft(token.decimals)
Expand All @@ -370,7 +382,7 @@ internal class KaspaWalletManager(
amount = Amount(
value = transactionAmount,
blockchain = blockchain,
type = AmountType.Token(token),
type = type,
),
fee = Fee.Kaspa(
amount = feeAmount,
Expand Down Expand Up @@ -398,7 +410,7 @@ internal class KaspaWalletManager(
(transactionData.extras as KaspaKRC20TransactionExtras).incompleteTokenTransactionParams
val feeAmount = (transactionData.fee as Fee.Kaspa).revealTransactionFee
val redeemScript = RedeemScript(
wallet.publicKey.blockchainKey,
wallet.publicKey.blockchainKey.toCompressedPublicKey(),
incompleteTokenTransactionParams.envelope,
)
val transaction = transactionBuilder.buildKRC20RevealTransactionToSign(
Expand Down Expand Up @@ -461,7 +473,10 @@ internal class KaspaWalletManager(
return dataStorage.getOrNull(token.createKey())
}

private suspend fun store(token: Token, data: BlockchainSavedData.KaspaKRC20IncompleteTokenTransaction) {
private suspend fun storeIncompleteTokenTransaction(
token: Token,
data: BlockchainSavedData.KaspaKRC20IncompleteTokenTransaction,
) {
dataStorage.store(token.createKey(), data)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package com.tangem.blockchain.blockchains.kaspa.krc20
import com.tangem.blockchain.blockchains.kaspa.krc20.model.IncompleteTokenTransactionParams
import com.tangem.blockchain.common.TransactionExtras

data class KaspaKRC20TransactionExtras(
internal data class KaspaKRC20TransactionExtras(
val incompleteTokenTransactionParams: IncompleteTokenTransactionParams,
) : TransactionExtras
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.tangem.blockchain.blockchains.kaspa.krc20.model

import com.tangem.blockchain.blockchains.kaspa.KaspaTransaction

data class CommitTransaction(
internal data class CommitTransaction(
val transaction: KaspaTransaction,
val hashes: List<ByteArray>,
val redeemScript: RedeemScript,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Envelope(
internal data class Envelope(
@Json(name = "p") val p: String,
@Json(name = "op") val op: String,
@Json(name = "amt") val amt: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.tangem.blockchain.blockchains.kaspa.krc20.model

import java.math.BigDecimal

data class IncompleteTokenTransactionParams(
internal data class IncompleteTokenTransactionParams(
val transactionId: String,
val amountValue: BigDecimal,
val feeAmountValue: BigDecimal,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.tangem.blockchain.blockchains.kaspa.krc20.model

data class RedeemScript(
internal data class RedeemScript(
val publicKey: ByteArray,
val envelope: Envelope,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface AssetRequirementsManager {

suspend fun fulfillRequirements(currencyType: CryptoCurrencyType, signer: TransactionSigner): SimpleResult

suspend fun discardRequirements(currencyType: CryptoCurrencyType) { }
suspend fun discardRequirements(currencyType: CryptoCurrencyType): SimpleResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,11 @@ class KaspaTransactionTest {

// act
val buildToSignResult = transactionBuilder.buildToSign(transactionData) as Result.Success
val signedTransaction = transactionBuilder.buildToSend(signature)
val hashes = transactionBuilder.getHashesForSign(buildToSignResult.data)
val signedTransaction = transactionBuilder.buildToSend(signature, buildToSignResult.data)

// assert
Truth.assertThat(buildToSignResult.data.map { it.toList() })
Truth.assertThat(hashes.map { it.toList() })
.containsExactly(expectedHashToSign1, expectedHashToSign2, expectedHashToSign3)
Truth.assertThat(signedTransaction).isEqualTo(expectedSignedTransaction)
}
Expand Down Expand Up @@ -228,10 +229,11 @@ class KaspaTransactionTest {

// act
val buildToSignResult = transactionBuilder.buildToSign(transactionData) as Result.Success
val signedTransaction = transactionBuilder.buildToSend(signature)
val hashes = transactionBuilder.getHashesForSign(buildToSignResult.data)
val signedTransaction = transactionBuilder.buildToSend(signature, buildToSignResult.data)

// assert
Truth.assertThat(buildToSignResult.data.map { it.toList() }).containsExactly(expectedHashToSign1)
Truth.assertThat(hashes.map { it.toList() }).containsExactly(expectedHashToSign1)
Truth.assertThat(signedTransaction).isEqualTo(expectedSignedTransaction)
}

Expand Down

0 comments on commit 4908301

Please sign in to comment.